home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Resources / Developers / XAMPP 1.5.4 / Windows installer / xampp-win32-1.5.4-installer.exe / xampp / php / pear / DB / DataObject.php < prev    next >
Encoding:
PHP Script  |  2006-04-07  |  139.6 KB  |  4,033 lines

  1. <?php
  2. /**
  3.  * Object Based Database Query Builder and data store
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   Database
  14.  * @package    DB_DataObject
  15.  * @author     Alan Knowles <alan@akbkhome.com>
  16.  * @copyright  1997-2006 The PHP Group
  17.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  18.  * @version    CVS: $Id: DataObject.php,v 1.410 2006/03/06 01:31:40 alan_k Exp $
  19.  * @link       http://pear.php.net/package/DB_DataObject
  20.  */
  21.   
  22.  
  23. /* =========================================================================== 
  24.  *
  25.  *    !!!!!!!!!!!!!               W A R N I N G                !!!!!!!!!!!
  26.  *
  27.  *  THIS MAY SEGFAULT PHP IF YOU ARE USING THE ZEND OPTIMIZER (to fix it, 
  28.  *  just add "define('DB_DATAOBJECT_NO_OVERLOAD',true);" before you include 
  29.  *  this file. reducing the optimization level may also solve the segfault.
  30.  *  ===========================================================================
  31.  */
  32.  
  33. /**
  34.  * The main "DB_DataObject" class is really a base class for your own tables classes
  35.  *
  36.  * // Set up the class by creating an ini file (refer to the manual for more details
  37.  * [DB_DataObject]
  38.  * database         = mysql:/username:password@host/database
  39.  * schema_location = /home/myapplication/database
  40.  * class_location  = /home/myapplication/DBTables/
  41.  * clase_prefix    = DBTables_
  42.  *
  43.  *
  44.  * //Start and initialize...................... - dont forget the &
  45.  * $config = parse_ini_file('example.ini',true);
  46.  * $options = &PEAR::getStaticProperty('DB_DataObject','options');
  47.  * $options = $config['DB_DataObject'];
  48.  *
  49.  * // example of a class (that does not use the 'auto generated tables data')
  50.  * class mytable extends DB_DataObject {
  51.  *     // mandatory - set the table
  52.  *     var $_database_dsn = "mysql://username:password@localhost/database";
  53.  *     var $__table = "mytable";
  54.  *     function table() {
  55.  *         return array(
  56.  *             'id' => 1, // integer or number
  57.  *             'name' => 2, // string
  58.  *        );
  59.  *     }
  60.  *     function keys() {
  61.  *         return array('id');
  62.  *     }
  63.  * }
  64.  *
  65.  * // use in the application
  66.  *
  67.  *
  68.  * Simple get one row
  69.  *
  70.  * $instance = new mytable;
  71.  * $instance->get("id",12);
  72.  * echo $instance->somedata;
  73.  *
  74.  *
  75.  * Get multiple rows
  76.  *
  77.  * $instance = new mytable;
  78.  * $instance->whereAdd("ID > 12");
  79.  * $instance->whereAdd("ID < 14");
  80.  * $instance->find();
  81.  * while ($instance->fetch()) {
  82.  *     echo $instance->somedata;
  83.  * }
  84.  
  85.  
  86. /**
  87.  * Needed classes
  88.  * - we use getStaticProperty from PEAR pretty extensively (cant remove it ATM)
  89.  */
  90.  
  91. require_once 'PEAR.php';
  92.  
  93. /**
  94.  * We are duping fetchmode constants to be compatible with
  95.  * both DB and MDB2
  96.  */
  97. define('DB_DATAOBJECT_FETCHMODE_ORDERED',1); 
  98. define('DB_DATAOBJECT_FETCHMODE_ASSOC',2);
  99.  
  100.  
  101.  
  102.  
  103.  
  104. /**
  105.  * these are constants for the get_table array
  106.  * user to determine what type of escaping is required around the object vars.
  107.  */
  108. define('DB_DATAOBJECT_INT',  1);  // does not require ''
  109. define('DB_DATAOBJECT_STR',  2);  // requires ''
  110.  
  111. define('DB_DATAOBJECT_DATE', 4);  // is date #TODO
  112. define('DB_DATAOBJECT_TIME', 8);  // is time #TODO
  113. define('DB_DATAOBJECT_BOOL', 16); // is boolean #TODO
  114. define('DB_DATAOBJECT_TXT',  32); // is long text #TODO
  115. define('DB_DATAOBJECT_BLOB', 64); // is blob type
  116.  
  117.  
  118. define('DB_DATAOBJECT_NOTNULL', 128);           // not null col.
  119. define('DB_DATAOBJECT_MYSQLTIMESTAMP'   , 256);           // mysql timestamps (ignored by update/insert)
  120. /*
  121.  * Define this before you include DataObjects.php to  disable overload - if it segfaults due to Zend optimizer..
  122.  */
  123. //define('DB_DATAOBJECT_NO_OVERLOAD',true)  
  124.  
  125.  
  126. /**
  127.  * Theses are the standard error codes, most methods will fail silently - and return false
  128.  * to access the error message either use $table->_lastError
  129.  * or $last_error = PEAR::getStaticProperty('DB_DataObject','lastError');
  130.  * the code is $last_error->code, and the message is $last_error->message (a standard PEAR error)
  131.  */
  132.  
  133. define('DB_DATAOBJECT_ERROR_INVALIDARGS',   -1);  // wrong args to function
  134. define('DB_DATAOBJECT_ERROR_NODATA',        -2);  // no data available
  135. define('DB_DATAOBJECT_ERROR_INVALIDCONFIG', -3);  // something wrong with the config
  136. define('DB_DATAOBJECT_ERROR_NOCLASS',       -4);  // no class exists
  137. define('DB_DATAOBJECT_ERROR_INVALID_CALL'  ,-7);  // overlad getter/setter failure
  138.  
  139. /**
  140.  * Used in methods like delete() and count() to specify that the method should
  141.  * build the condition only out of the whereAdd's and not the object parameters.
  142.  */
  143. define('DB_DATAOBJECT_WHEREADD_ONLY', true);
  144.  
  145. /**
  146.  *
  147.  * storage for connection and result objects,
  148.  * it is done this way so that print_r()'ing the is smaller, and
  149.  * it reduces the memory size of the object.
  150.  * -- future versions may use $this->_connection = & PEAR object..
  151.  *   although will need speed tests to see how this affects it.
  152.  * - includes sub arrays
  153.  *   - connections = md5 sum mapp to pear db object
  154.  *   - results     = [id] => map to pear db object
  155.  *   - resultseq   = sequence id for results & results field
  156.  *   - resultfields = [id] => list of fields return from query (for use with toArray())
  157.  *   - ini         = mapping of database to ini file results
  158.  *   - links       = mapping of database to links file
  159.  *   - lasterror   = pear error objects for last error event.
  160.  *   - config      = aliased view of PEAR::getStaticPropery('DB_DataObject','options') * done for performance.
  161.  *   - array of loaded classes by autoload method - to stop it doing file access request over and over again!
  162.  */
  163. $GLOBALS['_DB_DATAOBJECT']['RESULTS']   = array();
  164. $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ'] = 1;
  165. $GLOBALS['_DB_DATAOBJECT']['RESULTFIELDS'] = array();
  166. $GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'] = array();
  167. $GLOBALS['_DB_DATAOBJECT']['INI'] = array();
  168. $GLOBALS['_DB_DATAOBJECT']['LINKS'] = array();
  169. $GLOBALS['_DB_DATAOBJECT']['SEQUENCE'] = array();
  170. $GLOBALS['_DB_DATAOBJECT']['LASTERROR'] = null;
  171. $GLOBALS['_DB_DATAOBJECT']['CONFIG'] = array();
  172. $GLOBALS['_DB_DATAOBJECT']['CACHE'] = array();
  173. $GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = false;
  174. $GLOBALS['_DB_DATAOBJECT']['QUERYENDTIME'] = 0;
  175.  
  176.  
  177.  
  178. // this will be horrifically slow!!!!
  179. // NOTE: Overload SEGFAULTS ON PHP4 + Zend Optimizer (see define before..)
  180. // these two are BC/FC handlers for call in PHP4/5
  181.  
  182. if ( substr(phpversion(),0,1) == 5) {
  183.     class DB_DataObject_Overload 
  184.     {
  185.         function __call($method,$args) 
  186.         {
  187.             $return = null;
  188.             $this->_call($method,$args,$return);
  189.             return $return;
  190.         }
  191.         function __sleep() 
  192.         {
  193.             return array_keys(get_object_vars($this)) ; 
  194.         }
  195.     }
  196. } else {
  197.     if (version_compare(phpversion(),'4.3.10','eq') && !defined('DB_DATAOBJECT_NO_OVERLOAD')) {
  198.         trigger_error(
  199.             "overload does not work with PHP4.3.10, either upgrade 
  200.             (snaps.php.net) or more recent version 
  201.             or define DB_DATAOBJECT_NO_OVERLOAD as per the manual.
  202.             ",E_USER_ERROR);
  203.     }
  204.  
  205.     if (!function_exists('clone')) {
  206.         // emulate clone  - as per php_compact, slow but really the correct behaviour..
  207.         eval('function clone($t) { $r = $t; if (method_exists($r,"__clone")) { $r->__clone(); } return $r; }');
  208.     }
  209.     eval('
  210.         class DB_DataObject_Overload {
  211.             function __call($method,$args,&$return) {
  212.                 return $this->_call($method,$args,$return); 
  213.             }
  214.         }
  215.     ');
  216. }
  217.  
  218.     
  219.  
  220.  
  221.  
  222.  
  223.  /*
  224.  *
  225.  * @package  DB_DataObject
  226.  * @author   Alan Knowles <alan@akbkhome.com>
  227.  * @since    PHP 4.0
  228.  */
  229.  
  230. class DB_DataObject extends DB_DataObject_Overload
  231. {
  232.    /**
  233.     * The Version - use this to check feature changes
  234.     *
  235.     * @access   private
  236.     * @var      string
  237.     */
  238.     var $_DB_DataObject_version = "1.8.4";
  239.  
  240.     /**
  241.      * The Database table (used by table extends)
  242.      *
  243.      * @access  private
  244.      * @var     string
  245.      */
  246.     var $__table = '';  // database table
  247.  
  248.     /**
  249.      * The Number of rows returned from a query
  250.      *
  251.      * @access  public
  252.      * @var     int
  253.      */
  254.     var $N = 0;  // Number of rows returned from a query
  255.  
  256.     /* ============================================================= */
  257.     /*                      Major Public Methods                     */
  258.     /* (designed to be optionally then called with parent::method()) */
  259.     /* ============================================================= */
  260.  
  261.  
  262.     /**
  263.      * Get a result using key, value.
  264.      *
  265.      * for example
  266.      * $object->get("ID",1234);
  267.      * Returns Number of rows located (usually 1) for success,
  268.      * and puts all the table columns into this classes variables
  269.      *
  270.      * see the fetch example on how to extend this.
  271.      *
  272.      * if no value is entered, it is assumed that $key is a value
  273.      * and get will then use the first key in keys()
  274.      * to obtain the key.
  275.      *
  276.      * @param   string  $k column
  277.      * @param   string  $v value
  278.      * @access  public
  279.      * @return  int     No. of rows
  280.      */
  281.     function get($k = null, $v = null)
  282.     {
  283.         global $_DB_DATAOBJECT;
  284.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  285.             DB_DataObject::_loadConfig();
  286.         }
  287.         $keys = array();
  288.         
  289.         if ($v === null) {
  290.             $v = $k;
  291.             $keys = $this->keys();
  292.             if (!$keys) {
  293.                 $this->raiseError("No Keys available for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  294.                 return false;
  295.             }
  296.             $k = $keys[0];
  297.         }
  298.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  299.             $this->debug("$k $v " .print_r($keys,true), "GET");
  300.         }
  301.         
  302.         if ($v === null) {
  303.             $this->raiseError("No Value specified for get", DB_DATAOBJECT_ERROR_INVALIDARGS);
  304.             return false;
  305.         }
  306.         $this->$k = $v;
  307.         return $this->find(1);
  308.     }
  309.  
  310.     /**
  311.      * An autoloading, caching static get method  using key, value (based on get)
  312.      *
  313.      * Usage:
  314.      * $object = DB_DataObject::staticGet("DbTable_mytable",12);
  315.      * or
  316.      * $object =  DB_DataObject::staticGet("DbTable_mytable","name","fred");
  317.      *
  318.      * or write it into your extended class:
  319.      * function &staticGet($k,$v=NULL) { return DB_DataObject::staticGet("This_Class",$k,$v);  }
  320.      *
  321.      * @param   string  $class class name
  322.      * @param   string  $k     column (or value if using keys)
  323.      * @param   string  $v     value (optional)
  324.      * @access  public
  325.      * @return  object
  326.      */
  327.     function &staticGet($class, $k, $v = null)
  328.     {
  329.         $lclass = strtolower($class);
  330.         global $_DB_DATAOBJECT;
  331.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  332.             DB_DataObject::_loadConfig();
  333.         }
  334.  
  335.         
  336.  
  337.         $key = "$k:$v";
  338.         if ($v === null) {
  339.             $key = $k;
  340.         }
  341.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  342.             DB_DataObject::debug("$class $key","STATIC GET - TRY CACHE");
  343.         }
  344.         if (!empty($_DB_DATAOBJECT['CACHE'][$lclass][$key])) {
  345.             return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
  346.         }
  347.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  348.             DB_DataObject::debug("$class $key","STATIC GET - NOT IN CACHE");
  349.         }
  350.  
  351.         $obj = DB_DataObject::factory(substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix'])));
  352.         if (PEAR::isError($obj)) {
  353.             DB_DataObject::raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS);
  354.             $r = false;
  355.             return $r;
  356.         }
  357.         
  358.         if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) {
  359.             $_DB_DATAOBJECT['CACHE'][$lclass] = array();
  360.         }
  361.         if (!$obj->get($k,$v)) {
  362.             DB_DataObject::raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA);
  363.             
  364.             $r = false;
  365.             return $r;
  366.         }
  367.         $_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj;
  368.         return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
  369.     }
  370.  
  371.     /**
  372.      * find results, either normal or crosstable
  373.      *
  374.      * for example
  375.      *
  376.      * $object = new mytable();
  377.      * $object->ID = 1;
  378.      * $object->find();
  379.      *
  380.      *
  381.      * will set $object->N to number of rows, and expects next command to fetch rows
  382.      * will return $object->N
  383.      *
  384.      * @param   boolean $n Fetch first result
  385.      * @access  public
  386.      * @return  mixed (number of rows returned, or true if numRows fetching is not supported)
  387.      */
  388.     function find($n = false)
  389.     {
  390.         global $_DB_DATAOBJECT;
  391.         if (!isset($this->_query)) {
  392.             $this->raiseError(
  393.                 "You cannot do two queries on the same object (copy it before finding)", 
  394.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  395.             return false;
  396.         }
  397.         
  398.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  399.             DB_DataObject::_loadConfig();
  400.         }
  401.  
  402.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  403.             $this->debug($n, "find",1);
  404.         }
  405.         if (!$this->__table) {
  406.             // xdebug can backtrace this!
  407.             trigger_error("NO \$__table SPECIFIED in class definition",E_USER_ERROR);
  408.         }
  409.         $this->N = 0;
  410.         $query_before = $this->_query;
  411.         $this->_build_condition($this->table()) ;
  412.         
  413.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  414.         $this->_connect();
  415.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  416.        
  417.         /* We are checking for method modifyLimitQuery as it is PEAR DB specific */
  418.         $sql = 'SELECT ' .
  419.             $this->_query['data_select'] . " \n" .
  420.             ' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " \n" .
  421.             $this->_join . " \n" .
  422.             $this->_query['condition'] . " \n" .
  423.             $this->_query['group_by']  . " \n" .
  424.             $this->_query['having']    . " \n" .
  425.             $this->_query['order_by']  . " \n";
  426.         
  427.         if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) || 
  428.             ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
  429.             /* PEAR DB specific */
  430.         
  431.             if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
  432.                 $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);
  433.             }
  434.         } else {
  435.             /* theoretically MDB2! */
  436.             if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
  437.                 $DB->setLimit($this->_query['limit_count'],$this->_query['limit_start']);
  438.             }
  439.         }
  440.         
  441.         
  442.         $this->_query($sql);
  443.         
  444.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  445.             $this->debug("CHECK autofetchd $n", "find", 1);
  446.         }
  447.         
  448.         // find(true)
  449.         
  450.         $ret = $this->N;
  451.         if (!$ret && !empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {     
  452.             // clear up memory if nothing found!?
  453.             unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  454.         }
  455.         
  456.         if ($n && $this->N > 0 ) {
  457.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  458.                 $this->debug("ABOUT TO AUTOFETCH", "find", 1);
  459.             }
  460.             $fs = $this->fetch();
  461.             // if fetch returns false (eg. failed), then the backend doesnt support numRows (eg. ret=true)
  462.             // - hence find() also returns false..
  463.             $ret = ($ret === true) ? $fs : $ret;
  464.         }
  465.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  466.             $this->debug("DONE", "find", 1);
  467.         }
  468.         $this->_query = $query_before;
  469.         return $ret;
  470.     }
  471.  
  472.     /**
  473.      * fetches next row into this objects var's
  474.      *
  475.      * returns 1 on success 0 on failure
  476.      *
  477.      *
  478.      *
  479.      * Example
  480.      * $object = new mytable();
  481.      * $object->name = "fred";
  482.      * $object->find();
  483.      * $store = array();
  484.      * while ($object->fetch()) {
  485.      *   echo $this->ID;
  486.      *   $store[] = $object; // builds an array of object lines.
  487.      * }
  488.      *
  489.      * to add features to a fetch
  490.      * function fetch () {
  491.      *    $ret = parent::fetch();
  492.      *    $this->date_formated = date('dmY',$this->date);
  493.      *    return $ret;
  494.      * }
  495.      *
  496.      * @access  public
  497.      * @return  boolean on success
  498.      */
  499.     function fetch()
  500.     {
  501.  
  502.         global $_DB_DATAOBJECT;
  503.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  504.             DB_DataObject::_loadConfig();
  505.         }
  506.         if (empty($this->N)) {
  507.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  508.                 $this->debug("No data returned from FIND (eg. N is 0)","FETCH", 3);
  509.             }
  510.             return false;
  511.         }
  512.         
  513.         if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]) || 
  514.             !is_object($result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) 
  515.         {
  516.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  517.                 $this->debug('fetched on object after fetch completed (no results found)');
  518.             }
  519.             return false;
  520.         }
  521.         
  522.         
  523.         $array = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ASSOC);
  524.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  525.             $this->debug(serialize($array),"FETCH");
  526.         }
  527.         
  528.         // fetched after last row..
  529.         if ($array === null) {
  530.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  531.                 $t= explode(' ',microtime());
  532.             
  533.                 $this->debug("Last Data Fetch'ed after " . 
  534.                         ($t[0]+$t[1]- $_DB_DATAOBJECT['QUERYENDTIME']  ) . 
  535.                         " seconds",
  536.                     "FETCH", 1);
  537.             }
  538.             // reduce the memory usage a bit... (but leave the id in, so count() works ok on it)
  539.             unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  540.             
  541.             // we need to keep a copy of resultfields locally so toArray() still works
  542.             // however we dont want to keep it in the global cache..
  543.             
  544.             if (!empty($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  545.                 $this->_resultFields = $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid];
  546.                 unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]);
  547.             }
  548.             // this is probably end of data!!
  549.             //DB_DataObject::raiseError("fetch: no data returned", DB_DATAOBJECT_ERROR_NODATA);
  550.             return false;
  551.         }
  552.         // make sure resultFields is always empty..
  553.         $this->_resultFields = false;
  554.         
  555.         if (!isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  556.             // note: we dont declare this to keep the print_r size down.
  557.             $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]= array_flip(array_keys($array));
  558.         }
  559.         
  560.         foreach($array as $k=>$v) {
  561.             $kk = str_replace(".", "_", $k);
  562.             $kk = str_replace(" ", "_", $kk);
  563.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  564.                 $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
  565.             }
  566.             $this->$kk = $array[$k];
  567.         }
  568.         
  569.         // set link flag
  570.         $this->_link_loaded=false;
  571.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  572.             $this->debug("{$this->__table} DONE", "fetchrow",2);
  573.         }
  574.         if (isset($this->_query) &&  empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) {
  575.             unset($this->_query);
  576.         }
  577.         return true;
  578.     }
  579.  
  580.     /**
  581.      * Adds a condition to the WHERE statement, defaults to AND
  582.      *
  583.      * $object->whereAdd(); //reset or cleaer ewhwer
  584.      * $object->whereAdd("ID > 20");
  585.      * $object->whereAdd("age > 20","OR");
  586.      *
  587.      * @param    string  $cond  condition
  588.      * @param    string  $logic optional logic "OR" (defaults to "AND")
  589.      * @access   public
  590.      * @return   string|PEAR::Error - previous condition or Error when invalid args found
  591.      */
  592.     function whereAdd($cond = false, $logic = 'AND')
  593.     {
  594.         if (!isset($this->_query)) {
  595.             return $this->raiseError(
  596.                 "You cannot do two queries on the same object (clone it before finding)", 
  597.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  598.         }
  599.         
  600.         if ($cond === false) {
  601.             $r = $this->_query['condition'];
  602.             $this->_query['condition'] = '';
  603.             return preg_replace('/^\s+WHERE\s+/','',$r);
  604.         }
  605.         // check input...= 0 or '   ' == error!
  606.         if (!trim($cond)) {
  607.             return $this->raiseError("WhereAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  608.         }
  609.         $r = $this->_query['condition'];
  610.         if ($this->_query['condition']) {
  611.             $this->_query['condition'] .= " {$logic} ( {$cond} )";
  612.             return $r;
  613.         }
  614.         $this->_query['condition'] = " WHERE ( {$cond} ) ";
  615.         return $r;
  616.     }
  617.  
  618.     /**
  619.      * Adds a order by condition
  620.      *
  621.      * $object->orderBy(); //clears order by
  622.      * $object->orderBy("ID");
  623.      * $object->orderBy("ID,age");
  624.      *
  625.      * @param  string $order  Order
  626.      * @access public
  627.      * @return none|PEAR::Error - invalid args only
  628.      */
  629.     function orderBy($order = false)
  630.     {
  631.         if (!isset($this->_query)) {
  632.             $this->raiseError(
  633.                 "You cannot do two queries on the same object (copy it before finding)", 
  634.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  635.             return false;
  636.         }
  637.         if ($order === false) {
  638.             $this->_query['order_by'] = '';
  639.             return;
  640.         }
  641.         // check input...= 0 or '    ' == error!
  642.         if (!trim($order)) {
  643.             return $this->raiseError("orderBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  644.         }
  645.         
  646.         if (!$this->_query['order_by']) {
  647.             $this->_query['order_by'] = " ORDER BY {$order} ";
  648.             return;
  649.         }
  650.         $this->_query['order_by'] .= " , {$order}";
  651.     }
  652.  
  653.     /**
  654.      * Adds a group by condition
  655.      *
  656.      * $object->groupBy(); //reset the grouping
  657.      * $object->groupBy("ID DESC");
  658.      * $object->groupBy("ID,age");
  659.      *
  660.      * @param  string  $group  Grouping
  661.      * @access public
  662.      * @return none|PEAR::Error - invalid args only
  663.      */
  664.     function groupBy($group = false)
  665.     {
  666.         if (!isset($this->_query)) {
  667.             $this->raiseError(
  668.                 "You cannot do two queries on the same object (copy it before finding)", 
  669.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  670.             return false;
  671.         }
  672.         if ($group === false) {
  673.             $this->_query['group_by'] = '';
  674.             return;
  675.         }
  676.         // check input...= 0 or '    ' == error!
  677.         if (!trim($group)) {
  678.             return $this->raiseError("groupBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  679.         }
  680.         
  681.         
  682.         if (!$this->_query['group_by']) {
  683.             $this->_query['group_by'] = " GROUP BY {$group} ";
  684.             return;
  685.         }
  686.         $this->_query['group_by'] .= " , {$group}";
  687.     }
  688.  
  689.     /**
  690.      * Adds a having clause
  691.      *
  692.      * $object->having(); //reset the grouping
  693.      * $object->having("sum(value) > 0 ");
  694.      *
  695.      * @param  string  $having  condition
  696.      * @access public
  697.      * @return none|PEAR::Error - invalid args only
  698.      */
  699.     function having($having = false)
  700.     {
  701.         if (!isset($this->_query)) {
  702.             $this->raiseError(
  703.                 "You cannot do two queries on the same object (copy it before finding)", 
  704.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  705.             return false;
  706.         }
  707.         if ($having === false) {
  708.             $this->_query['having'] = '';
  709.             return;
  710.         }
  711.         // check input...= 0 or '    ' == error!
  712.         if (!trim($having)) {
  713.             return $this->raiseError("Having: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  714.         }
  715.         
  716.         
  717.         if (!$this->_query['having']) {
  718.             $this->_query['having'] = " HAVING {$having} ";
  719.             return;
  720.         }
  721.         $this->_query['having'] .= " AND {$having}";
  722.     }
  723.  
  724.     /**
  725.      * Sets the Limit
  726.      *
  727.      * $boject->limit(); // clear limit
  728.      * $object->limit(12);
  729.      * $object->limit(12,10);
  730.      *
  731.      * Note this will emit an error on databases other than mysql/postgress
  732.      * as there is no 'clean way' to implement it. - you should consider refering to
  733.      * your database manual to decide how you want to implement it.
  734.      *
  735.      * @param  string $a  limit start (or number), or blank to reset
  736.      * @param  string $b  number
  737.      * @access public
  738.      * @return none|PEAR::Error - invalid args only
  739.      */
  740.     function limit($a = null, $b = null)
  741.     {
  742.         if (!isset($this->_query)) {
  743.             $this->raiseError(
  744.                 "You cannot do two queries on the same object (copy it before finding)", 
  745.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  746.             return false;
  747.         }
  748.         
  749.         if ($a === null) {
  750.            $this->_query['limit_start'] = '';
  751.            $this->_query['limit_count'] = '';
  752.            return;
  753.         }
  754.         // check input...= 0 or '    ' == error!
  755.         if ((!is_int($a) && ((string)((int)$a) !== (string)$a)) 
  756.             || (($b !== null) && (!is_int($b) && ((string)((int)$b) !== (string)$b)))) {
  757.             return $this->raiseError("limit: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  758.         }
  759.         global $_DB_DATAOBJECT;
  760.         $this->_connect();
  761.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  762.         
  763.         $this->_query['limit_start'] = ($b == null) ? 0 : (int)$a;
  764.         $this->_query['limit_count'] = ($b == null) ? (int)$a : (int)$b;
  765.         
  766.     }
  767.  
  768.     /**
  769.      * Adds a select columns
  770.      *
  771.      * $object->selectAdd(); // resets select to nothing!
  772.      * $object->selectAdd("*"); // default select
  773.      * $object->selectAdd("unixtime(DATE) as udate");
  774.      * $object->selectAdd("DATE");
  775.      *
  776.      * to prepend distict:
  777.      * $object->selectAdd('distinct ' . $object->selectAdd());
  778.      *
  779.      * @param  string  $k
  780.      * @access public
  781.      * @return mixed null or old string if you reset it.
  782.      */
  783.     function selectAdd($k = null)
  784.     {
  785.         if (!isset($this->_query)) {
  786.             $this->raiseError(
  787.                 "You cannot do two queries on the same object (copy it before finding)", 
  788.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  789.             return false;
  790.         }
  791.         if ($k === null) {
  792.             $old = $this->_query['data_select'];
  793.             $this->_query['data_select'] = '';
  794.             return $old;
  795.         }
  796.         
  797.         // check input...= 0 or '    ' == error!
  798.         if (!trim($k)) {
  799.             return $this->raiseError("selectAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  800.         }
  801.         
  802.         if ($this->_query['data_select']) {
  803.             $this->_query['data_select'] .= ', ';
  804.         }
  805.         $this->_query['data_select'] .= " $k ";
  806.     }
  807.     /**
  808.      * Adds multiple Columns or objects to select with formating.
  809.      *
  810.      * $object->selectAs(null); // adds "table.colnameA as colnameA,table.colnameB as colnameB,......"
  811.      *                      // note with null it will also clear the '*' default select
  812.      * $object->selectAs(array('a','b'),'%s_x'); // adds "a as a_x, b as b_x"
  813.      * $object->selectAs(array('a','b'),'ddd_%s','ccc'); // adds "ccc.a as ddd_a, ccc.b as ddd_b"
  814.      * $object->selectAdd($object,'prefix_%s'); // calls $object->get_table and adds it all as
  815.      *                  objectTableName.colnameA as prefix_colnameA
  816.      *
  817.      * @param  array|object|null the array or object to take column names from.
  818.      * @param  string           format in sprintf format (use %s for the colname)
  819.      * @param  string           table name eg. if you have joinAdd'd or send $from as an array.
  820.      * @access public
  821.      * @return void
  822.      */
  823.     function selectAs($from = null,$format = '%s',$tableName=false)
  824.     {
  825.         global $_DB_DATAOBJECT;
  826.         
  827.         if (!isset($this->_query)) {
  828.             $this->raiseError(
  829.                 "You cannot do two queries on the same object (copy it before finding)", 
  830.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  831.             return false;
  832.         }
  833.         
  834.         if ($from === null) {
  835.             // blank the '*' 
  836.             $this->selectAdd();
  837.             $from = $this;
  838.         }
  839.         
  840.         
  841.         $table = $this->__table;
  842.         if (is_object($from)) {
  843.             $table = $from->__table;
  844.             $from = array_keys($from->table());
  845.         }
  846.         
  847.         if ($tableName !== false) {
  848.             $table = $tableName;
  849.         }
  850.         $s = '%s';
  851.         if (!empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers'])) {
  852.             $this->_connect();
  853.             $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  854.             $s      = $DB->quoteIdentifier($s);
  855.             $format = $DB->quoteIdentifier($format); 
  856.         }
  857.         foreach ($from as $k) {
  858.             $this->selectAdd(sprintf("{$s}.{$s} as {$format}",$table,$k,$k));
  859.         }
  860.         $this->_query['data_select'] .= "\n";
  861.     }
  862.     /**
  863.      * Insert the current objects variables into the database
  864.      *
  865.      * Returns the ID of the inserted element (if auto increment or sequences are used.)
  866.      *
  867.      * for example
  868.      *
  869.      * Designed to be extended
  870.      *
  871.      * $object = new mytable();
  872.      * $object->name = "fred";
  873.      * echo $object->insert();
  874.      *
  875.      * @access public
  876.      * @return mixed false on failure, int when auto increment or sequence used, otherwise true on success
  877.      */
  878.     function insert()
  879.     {
  880.         global $_DB_DATAOBJECT;
  881.         
  882.         // we need to write to the connection (For nextid) - so us the real
  883.         // one not, a copyied on (as ret-by-ref fails with overload!)
  884.         
  885.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  886.             $this->_connect();
  887.         }
  888.         
  889.         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  890.         
  891.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  892.          
  893.         $items =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
  894.             $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
  895.             
  896.         if (!$items) {
  897.             $this->raiseError("insert:No table definition for {$this->__table}",
  898.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  899.             return false;
  900.         }
  901.         $options = &$_DB_DATAOBJECT['CONFIG'];
  902.  
  903.  
  904.         $datasaved = 1;
  905.         $leftq     = '';
  906.         $rightq    = '';
  907.      
  908.         $seqKeys   = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table]) ?
  909.                         $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] : 
  910.                         $this->sequenceKey();
  911.         
  912.         $key       = isset($seqKeys[0]) ? $seqKeys[0] : false;
  913.         $useNative = isset($seqKeys[1]) ? $seqKeys[1] : false;
  914.         $seq       = isset($seqKeys[2]) ? $seqKeys[2] : false;
  915.         
  916.         $dbtype    = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["phptype"];
  917.         
  918.          
  919.         // nativeSequences or Sequences..     
  920.  
  921.         // big check for using sequences
  922.         
  923.         if (($key !== false) && !$useNative) { 
  924.         
  925.             if (!$seq) {
  926.                 $keyvalue =  $DB->nextId($this->__table);
  927.             } else {
  928.                 $f = $DB->getOption('seqname_format');
  929.                 $DB->setOption('seqname_format','%s');
  930.                 $keyvalue =  $DB->nextId($seq);
  931.                 $DB->setOption('seqname_format',$f);
  932.             }
  933.             if (PEAR::isError($keyvalue)) {
  934.                 $this->raiseError($keyvalue->toString(), DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  935.                 return false;
  936.             }
  937.             $this->$key = $keyvalue;
  938.         }
  939.  
  940.  
  941.  
  942.         foreach($items as $k => $v) {
  943.             
  944.             // if we are using autoincrement - skip the column...
  945.             if ($key && ($k == $key) && $useNative) {
  946.                 continue;
  947.             }
  948.         
  949.             
  950.             if (!isset($this->$k)) {
  951.                 continue;
  952.             }
  953.             // dont insert data into mysql timestamps 
  954.             // use query() if you really want to do this!!!!
  955.             if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
  956.                 continue;
  957.             }
  958.             
  959.             if ($leftq) {
  960.                 $leftq  .= ', ';
  961.                 $rightq .= ', ';
  962.             }
  963.             
  964.             $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ')  : "$k ");
  965.             
  966.             if (is_a($this->$k,'DB_DataObject_Cast')) {
  967.                 $value = $this->$k->toString($v,$DB);
  968.                 if (PEAR::isError($value)) {
  969.                     $this->raiseError($value->toString() ,DB_DATAOBJECT_ERROR_INVALIDARGS);
  970.                     return false;
  971.                 }
  972.                 $rightq .=  $value;
  973.                 continue;
  974.             }
  975.             
  976.             
  977.  
  978.             if (is_string($this->$k) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
  979.                 $rightq .= " NULL ";
  980.                 continue;
  981.             }
  982.             // DATE is empty... on a col. that can be null.. 
  983.             // note: this may be usefull for time as well..
  984.             if (!$this->$k && 
  985.                     (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && 
  986.                     !($v & DB_DATAOBJECT_NOTNULL)) {
  987.                     
  988.                 $rightq .= " NULL ";
  989.                 continue;
  990.             }
  991.               
  992.             
  993.             if ($v & DB_DATAOBJECT_STR) {
  994.                 $rightq .= $this->_quote((string) (
  995.                         ($v & DB_DATAOBJECT_BOOL) ? 
  996.                             // this is thanks to the braindead idea of postgres to 
  997.                             // use t/f for boolean.
  998.                             (($this->$k == 'f') ? 0 : (int)(bool) $this->$k) :  
  999.                             $this->$k
  1000.                     )) . " ";
  1001.                 continue;
  1002.             }
  1003.             if (is_numeric($this->$k)) {
  1004.                 $rightq .=" {$this->$k} ";
  1005.                 continue;
  1006.             }
  1007.             /* flag up string values - only at debug level... !!!??? */
  1008.             if (is_object($this->$k) || is_array($this->$k)) {
  1009.                 $this->debug('ODD DATA: ' .$k . ' ' .  print_r($this->$k,true),'ERROR');
  1010.             }
  1011.             
  1012.             // at present we only cast to integers
  1013.             // - V2 may store additional data about float/int
  1014.             $rightq .= ' ' . intval($this->$k) . ' ';
  1015.  
  1016.         }
  1017.         
  1018.         // not sure why we let empty insert here.. - I guess to generate a blank row..
  1019.         
  1020.         
  1021.         if ($leftq || $useNative) {
  1022.             $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table)    : $this->__table);
  1023.             
  1024.             $r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) ");
  1025.  
  1026.             
  1027.             
  1028.             if (PEAR::isError($r)) {
  1029.                 $this->raiseError($r);
  1030.                 return false;
  1031.             }
  1032.             
  1033.             if ($r < 1) {
  1034.                 return 0;
  1035.             }
  1036.             
  1037.             
  1038.             // now do we have an integer key!
  1039.             
  1040.             if ($key && $useNative) {
  1041.                 switch ($dbtype) {
  1042.                     case 'mysql':
  1043.                     case 'mysqli':
  1044.                         $method = "{$dbtype}_insert_id";
  1045.                         $this->$key = $method(
  1046.                             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection
  1047.                         );
  1048.                         break;
  1049.                     
  1050.                     case 'mssql':
  1051.                         // note this is not really thread safe - you should wrapp it with 
  1052.                         // transactions = eg.
  1053.                         // $db->query('BEGIN');
  1054.                         // $db->insert();
  1055.                         // $db->query('COMMIT');
  1056.                         
  1057.                         $mssql_key = $DB->getOne("SELECT @@IDENTITY");
  1058.                         if (PEAR::isError($mssql_key)) {
  1059.                             $this->raiseError($r);
  1060.                             return false;
  1061.                         }
  1062.                         $this->$key = $mssql_key;
  1063.                         break; 
  1064.                         
  1065.                     case 'pgsql':
  1066.                         if (!$seq) {
  1067.                             $seq = $DB->getSequenceName($this->__table );
  1068.                         }
  1069.                         $pgsql_key = $DB->getOne("SELECT last_value FROM ".$seq);
  1070.                         if (PEAR::isError($pgsql_key)) {
  1071.                             $this->raiseError($r);
  1072.                             return false;
  1073.                         }
  1074.                         $this->$key = $pgsql_key;
  1075.                         break;
  1076.                     
  1077.                     case 'ifx':
  1078.                         $this->$key = array_shift (
  1079.                             ifx_fetch_row (
  1080.                                 ifx_query(
  1081.                                     "select DBINFO('sqlca.sqlerrd1') FROM systables where tabid=1",
  1082.                                     $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection,
  1083.                                     IFX_SCROLL
  1084.                                 ), 
  1085.                                 "FIRST"
  1086.                             )
  1087.                         ); 
  1088.                         break;
  1089.                     
  1090.                 }
  1091.                         
  1092.             }
  1093.  
  1094.             if (isset($_DB_DATAOBJECT['CACHE'][strtolower(get_class($this))])) {
  1095.                 $this->_clear_cache();
  1096.             }
  1097.             if ($key) {
  1098.                 return $this->$key;
  1099.             }
  1100.             return true;
  1101.         }
  1102.         $this->raiseError("insert: No Data specifed for query", DB_DATAOBJECT_ERROR_NODATA);
  1103.         return false;
  1104.     }
  1105.  
  1106.     /**
  1107.      * Updates  current objects variables into the database
  1108.      * uses the keys() to decide how to update
  1109.      * Returns the  true on success
  1110.      *
  1111.      * for example
  1112.      *
  1113.      * $object = DB_DataObject::factory('mytable');
  1114.      * $object->get("ID",234);
  1115.      * $object->email="testing@test.com";
  1116.      * if(!$object->update())
  1117.      *   echo "UPDATE FAILED";
  1118.      *
  1119.      * to only update changed items :
  1120.      * $dataobject->get(132);
  1121.      * $original = $dataobject; // clone/copy it..
  1122.      * $dataobject->setFrom($_POST);
  1123.      * if ($dataobject->validate()) {
  1124.      *    $dataobject->update($original);
  1125.      * } // otherwise an error...
  1126.      *
  1127.      * performing global updates:
  1128.      * $object = DB_DataObject::factory('mytable');
  1129.      * $object->status = "dead";
  1130.      * $object->whereAdd('age > 150');
  1131.      * $object->update(DB_DATAOBJECT_WHEREADD_ONLY);
  1132.      *
  1133.      * @param object dataobject (optional) | DB_DATAOBJECT_WHEREADD_ONLY - used to only update changed items.
  1134.      * @access public
  1135.      * @return  int rows affected or false on failure
  1136.      */
  1137.     function update($dataObject = false)
  1138.     {
  1139.         global $_DB_DATAOBJECT;
  1140.         // connect will load the config!
  1141.         $this->_connect();
  1142.         
  1143.         
  1144.         $original_query = isset($this->_query) ? $this->_query : null;
  1145.         
  1146.         $items =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
  1147.             $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
  1148.         
  1149.         // only apply update against sequence key if it is set?????
  1150.         
  1151.         $seq    = $this->sequenceKey();
  1152.         if ($seq[0] !== false) {
  1153.             $keys = array($seq[0]);
  1154.             if (empty($this->{$keys[0]}) && $dataObject !== true) {
  1155.                 $this->raiseError("update: trying to perform an update without 
  1156.                         the key set, and argument to update is not 
  1157.                         DB_DATAOBJECT_WHEREADD_ONLY
  1158.                     ", DB_DATAOBJECT_ERROR_INVALIDARGS);
  1159.                 return false;  
  1160.             }
  1161.         } else {
  1162.             $keys = $this->keys();
  1163.         }
  1164.         
  1165.          
  1166.         if (!$items) {
  1167.             $this->raiseError("update:No table definition for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  1168.             return false;
  1169.         }
  1170.         $datasaved = 1;
  1171.         $settings  = '';
  1172.         $this->_connect();
  1173.         
  1174.         $DB            = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1175.         $dbtype        = $DB->dsn["phptype"];
  1176.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1177.         
  1178.         foreach($items as $k => $v) {
  1179.             if (!isset($this->$k)) {
  1180.                 continue;
  1181.             }
  1182.             // ignore stuff thats 
  1183.           
  1184.             // dont write things that havent changed..
  1185.             if (($dataObject !== false) && isset($dataObject->$k) && ($dataObject->$k === $this->$k)) {
  1186.                 continue;
  1187.             }
  1188.             
  1189.             // - dont write keys to left.!!!
  1190.             if (in_array($k,$keys)) {
  1191.                 continue;
  1192.             }
  1193.             
  1194.              // dont insert data into mysql timestamps 
  1195.             // use query() if you really want to do this!!!!
  1196.             if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
  1197.                 continue;
  1198.             }
  1199.             
  1200.             
  1201.             if ($settings)  {
  1202.                 $settings .= ', ';
  1203.             }
  1204.             
  1205.             $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
  1206.             
  1207.             if (is_a($this->$k,'DB_DataObject_Cast')) {
  1208.                 $value = $this->$k->toString($v,$DB);
  1209.                 if (PEAR::isError($value)) {
  1210.                     $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
  1211.                     return false;
  1212.                 }
  1213.                 $settings .= "$kSql = $value ";
  1214.                 continue;
  1215.             }
  1216.             
  1217.             // special values ... at least null is handled...
  1218.             if ((strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
  1219.                 $settings .= "$kSql = NULL ";
  1220.                 continue;
  1221.             }
  1222.             // DATE is empty... on a col. that can be null.. 
  1223.             // note: this may be usefull for time as well..
  1224.             if (!$this->$k && 
  1225.                     (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && 
  1226.                     !($v & DB_DATAOBJECT_NOTNULL)) {
  1227.                     
  1228.                 $settings .= "$kSql = NULL ";
  1229.                 continue;
  1230.             }
  1231.             
  1232.  
  1233.             if ($v & DB_DATAOBJECT_STR) {
  1234.                 $settings .= "$kSql = ". $this->_quote((string) (
  1235.                         ($v & DB_DATAOBJECT_BOOL) ? 
  1236.                             // this is thanks to the braindead idea of postgres to 
  1237.                             // use t/f for boolean.
  1238.                             (($this->$k == 'f') ? 0 : (int)(bool) $this->$k) :  
  1239.                             $this->$k
  1240.                     )) . ' ';
  1241.                 continue;
  1242.             }
  1243.             if (is_numeric($this->$k)) {
  1244.                 $settings .= "$kSql = {$this->$k} ";
  1245.                 continue;
  1246.             }
  1247.             // at present we only cast to integers
  1248.             // - V2 may store additional data about float/int
  1249.             $settings .= "$kSql = " . intval($this->$k) . ' ';
  1250.         }
  1251.  
  1252.         
  1253.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1254.             $this->debug("got keys as ".serialize($keys),3);
  1255.         }
  1256.         if ($dataObject !== true) {
  1257.             $this->_build_condition($items,$keys);
  1258.         } else {
  1259.             // prevent wiping out of data!
  1260.             if (empty($this->_query['condition'])) {
  1261.                  $this->raiseError("update: global table update not available
  1262.                         do \$do->whereAdd('1=1'); if you really want to do that.
  1263.                     ", DB_DATAOBJECT_ERROR_INVALIDARGS);
  1264.                 return false;
  1265.             }
  1266.         }
  1267.         
  1268.         
  1269.         
  1270.         //  echo " $settings, $this->condition ";
  1271.         if ($settings && isset($this->_query) && $this->_query['condition']) {
  1272.             
  1273.             $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
  1274.         
  1275.             $r = $this->_query("UPDATE  {$table}  SET {$settings} {$this->_query['condition']} ");
  1276.             
  1277.             // restore original query conditions.
  1278.             $this->_query = $original_query;
  1279.             
  1280.             if (PEAR::isError($r)) {
  1281.                 $this->raiseError($r);
  1282.                 return false;
  1283.             }
  1284.             if ($r < 1) {
  1285.                 return 0;
  1286.             }
  1287.  
  1288.             $this->_clear_cache();
  1289.             return $r;
  1290.         }
  1291.         // restore original query conditions.
  1292.         $this->_query = $original_query;
  1293.         
  1294.         // if you manually specified a dataobject, and there where no changes - then it's ok..
  1295.         if ($dataObject !== false) {
  1296.             return true;
  1297.         }
  1298.         
  1299.         $this->raiseError(
  1300.             "update: No Data specifed for query $settings , {$this->_query['condition']}", 
  1301.             DB_DATAOBJECT_ERROR_NODATA);
  1302.         return false;
  1303.     }
  1304.  
  1305.     /**
  1306.      * Deletes items from table which match current objects variables
  1307.      *
  1308.      * Returns the true on success
  1309.      *
  1310.      * for example
  1311.      *
  1312.      * Designed to be extended
  1313.      *
  1314.      * $object = new mytable();
  1315.      * $object->ID=123;
  1316.      * echo $object->delete(); // builds a conditon
  1317.      *
  1318.      * $object = new mytable();
  1319.      * $object->whereAdd('age > 12');
  1320.      * $object->limit(1);
  1321.      * $object->orderBy('age DESC');
  1322.      * $object->delete(true); // dont use object vars, use the conditions, limit and order.
  1323.      *
  1324.      * @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
  1325.      *             we will build the condition only using the whereAdd's.  Default is to
  1326.      *             build the condition only using the object parameters.
  1327.      *
  1328.      * @access public
  1329.      * @return mixed True on success, false on failure, 0 on no data affected
  1330.      */
  1331.     function delete($useWhere = false)
  1332.     {
  1333.         global $_DB_DATAOBJECT;
  1334.         // connect will load the config!
  1335.         $this->_connect();
  1336.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1337.         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1338.         
  1339.         $extra_cond = ' ' . (isset($this->_query['order_by']) ? $this->_query['order_by'] : ''); 
  1340.         
  1341.         if (!$useWhere) {
  1342.  
  1343.             $keys = $this->keys();
  1344.             $this->_query = array(); // as it's probably unset!
  1345.             $this->_query['condition'] = ''; // default behaviour not to use where condition
  1346.             $this->_build_condition($this->table(),$keys);
  1347.             // if primary keys are not set then use data from rest of object.
  1348.             if (!$this->_query['condition']) {
  1349.                 $this->_build_condition($this->table(),array(),$keys);
  1350.             }
  1351.             $extra_cond = '';
  1352.         } 
  1353.             
  1354.  
  1355.         // don't delete without a condition
  1356.         if (isset($this->_query) && $this->_query['condition']) {
  1357.         
  1358.             $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
  1359.             $sql = "DELETE FROM {$table} {$this->_query['condition']}{$extra_cond}";
  1360.             
  1361.             // add limit..
  1362.             
  1363.             if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
  1364.                 
  1365.                 if (!isset($_DB_DATAOBJECT['CONFIG']['db_driver']) ||  
  1366.                     ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
  1367.                     // pear DB 
  1368.                     $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);
  1369.                     
  1370.                 } else {
  1371.                     // MDB2
  1372.                     $DB->setLimit( $this->_query['limit_count'],$this->_query['limit_start']);
  1373.                 }
  1374.                     
  1375.             }
  1376.             
  1377.             
  1378.             $r = $this->_query($sql);
  1379.             
  1380.             
  1381.             if (PEAR::isError($r)) {
  1382.                 $this->raiseError($r);
  1383.                 return false;
  1384.             }
  1385.             if ($r < 1) {
  1386.                 return 0;
  1387.             }
  1388.             $this->_clear_cache();
  1389.             return $r;
  1390.         } else {
  1391.             $this->raiseError("delete: No condition specifed for query", DB_DATAOBJECT_ERROR_NODATA);
  1392.             return false;
  1393.         }
  1394.     }
  1395.  
  1396.     /**
  1397.      * fetches a specific row into this object variables
  1398.      *
  1399.      * Not recommended - better to use fetch()
  1400.      *
  1401.      * Returens true on success
  1402.      *
  1403.      * @param  int   $row  row
  1404.      * @access public
  1405.      * @return boolean true on success
  1406.      */
  1407.     function fetchRow($row = null)
  1408.     {
  1409.         global $_DB_DATAOBJECT;
  1410.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  1411.             $this->_loadConfig();
  1412.         }
  1413.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1414.             $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
  1415.         }
  1416.         if (!$this->__table) {
  1417.             $this->raiseError("fetchrow: No table", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  1418.             return false;
  1419.         }
  1420.         if ($row === null) {
  1421.             $this->raiseError("fetchrow: No row specified", DB_DATAOBJECT_ERROR_INVALIDARGS);
  1422.             return false;
  1423.         }
  1424.         if (!$this->N) {
  1425.             $this->raiseError("fetchrow: No results avaiable", DB_DATAOBJECT_ERROR_NODATA);
  1426.             return false;
  1427.         }
  1428.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1429.             $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
  1430.         }
  1431.  
  1432.  
  1433.         $result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
  1434.         $array  = $result->fetchrow(DB_DATAOBJECT_FETCHMODE_ASSOC,$row);
  1435.         if (!is_array($array)) {
  1436.             $this->raiseError("fetchrow: No results available", DB_DATAOBJECT_ERROR_NODATA);
  1437.             return false;
  1438.         }
  1439.  
  1440.         foreach($array as $k => $v) {
  1441.             $kk = str_replace(".", "_", $k);
  1442.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1443.                 $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
  1444.             }
  1445.             $this->$kk = $array[$k];
  1446.         }
  1447.  
  1448.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1449.             $this->debug("{$this->__table} DONE", "fetchrow", 3);
  1450.         }
  1451.         return true;
  1452.     }
  1453.  
  1454.     /**
  1455.      * Find the number of results from a simple query
  1456.      *
  1457.      * for example
  1458.      *
  1459.      * $object = new mytable();
  1460.      * $object->name = "fred";
  1461.      * echo $object->count();
  1462.      * echo $object->count(true);  // dont use object vars.
  1463.      * echo $object->count('distinct mycol');   count distinct mycol.
  1464.      * echo $object->count('distinct mycol',true); // dont use object vars.
  1465.      * echo $object->count('distinct');      // count distinct id (eg. the primary key)
  1466.      *
  1467.      *
  1468.      * @param bool|string  (optional)
  1469.      *                  (true|false => see below not on whereAddonly)
  1470.      *                  (string)
  1471.      *                      "DISTINCT" => does a distinct count on the tables 'key' column
  1472.      *                      otherwise  => normally it counts primary keys - you can use 
  1473.      *                                    this to do things like $do->count('distinct mycol');
  1474.      *                  
  1475.      * @param bool      $whereAddOnly (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
  1476.      *                  we will build the condition only using the whereAdd's.  Default is to
  1477.      *                  build the condition using the object parameters as well.
  1478.      *                  
  1479.      * @access public
  1480.      * @return int
  1481.      */
  1482.     function count($countWhat = false,$whereAddOnly = false)
  1483.     {
  1484.         global $_DB_DATAOBJECT;
  1485.         
  1486.         if (is_bool($countWhat)) {
  1487.             $whereAddOnly = $countWhat;
  1488.         }
  1489.         
  1490.         $t = clone($this);
  1491.         
  1492.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1493.         
  1494.         $items   = $t->table();
  1495.         if (!isset($t->_query)) {
  1496.             $this->raiseError(
  1497.                 "You cannot do run count after you have run fetch()", 
  1498.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  1499.             return false;
  1500.         }
  1501.         $this->_connect();
  1502.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1503.        
  1504.  
  1505.         if (!$whereAddOnly && $items)  {
  1506.             $t->_build_condition($items);
  1507.         }
  1508.         $keys = $this->keys();
  1509.  
  1510.         if (!$keys[0] && !is_string($countWhat)) {
  1511.             $this->raiseError(
  1512.                 "You cannot do run count without keys - use \$do->keys('id');", 
  1513.                 DB_DATAOBJECT_ERROR_INVALIDARGS,PEAR_ERROR_DIE);
  1514.             return false;
  1515.             
  1516.         }
  1517.         $table   = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
  1518.         $key_col = ($quoteIdentifiers ? $DB->quoteIdentifier($keys[0]) : $keys[0]);
  1519.         $as      = ($quoteIdentifiers ? $DB->quoteIdentifier('DATAOBJECT_NUM') : 'DATAOBJECT_NUM');
  1520.         
  1521.         // support distinct on default keys.
  1522.         $countWhat = (strtoupper($countWhat) == 'DISTINCT') ? 
  1523.             "DISTINCT {$table}.{$key_col}" : $countWhat;
  1524.         
  1525.         $countWhat = is_string($countWhat) ? $countWhat : "{$table}.{$key_col}";
  1526.         
  1527.         $r = $t->_query(
  1528.             "SELECT count({$countWhat}) as $as
  1529.                 FROM $table {$t->_join} {$t->_query['condition']}");
  1530.         if (PEAR::isError($r)) {
  1531.             return false;
  1532.         }
  1533.          
  1534.         $result  = &$_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid];
  1535.         $l = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ORDERED);
  1536.         return (int) $l[0];
  1537.     }
  1538.  
  1539.     /**
  1540.      * sends raw query to database
  1541.      *
  1542.      * Since _query has to be a private 'non overwriteable method', this is a relay
  1543.      *
  1544.      * @param  string  $string  SQL Query
  1545.      * @access public
  1546.      * @return void or DB_Error
  1547.      */
  1548.     function query($string)
  1549.     {
  1550.         return $this->_query($string);
  1551.     }
  1552.  
  1553.  
  1554.     /**
  1555.      * an escape wrapper around DB->escapeSimple()
  1556.      * can be used when adding manual queries or clauses
  1557.      * eg.
  1558.      * $object->query("select * from xyz where abc like '". $object->escape($_GET['name']) . "'");
  1559.      *
  1560.      * @param  string  $string  value to be escaped 
  1561.      * @access public
  1562.      * @return string
  1563.      */
  1564.     function escape($string)
  1565.     {
  1566.         global $_DB_DATAOBJECT;
  1567.         $this->_connect();
  1568.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1569.         // mdb2 uses escape...
  1570.         $dd = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver'];
  1571.         return ($dd == 'DB') ? $DB->escapeSimple($string) : $DB->escape($string);
  1572.     }
  1573.  
  1574.     /* ==================================================== */
  1575.     /*        Major Private Vars                            */
  1576.     /* ==================================================== */
  1577.  
  1578.     /**
  1579.      * The Database connection dsn (as described in the PEAR DB)
  1580.      * only used really if you are writing a very simple application/test..
  1581.      * try not to use this - it is better stored in configuration files..
  1582.      *
  1583.      * @access  private
  1584.      * @var     string
  1585.      */
  1586.     var $_database_dsn = '';
  1587.  
  1588.     /**
  1589.      * The Database connection id (md5 sum of databasedsn)
  1590.      *
  1591.      * @access  private
  1592.      * @var     string
  1593.      */
  1594.     var $_database_dsn_md5 = '';
  1595.  
  1596.     /**
  1597.      * The Database name
  1598.      * created in __connection
  1599.      *
  1600.      * @access  private
  1601.      * @var  string
  1602.      */
  1603.     var $_database = '';
  1604.  
  1605.     
  1606.     
  1607.     /**
  1608.      * The QUERY rules
  1609.      * This replaces alot of the private variables 
  1610.      * used to build a query, it is unset after find() is run.
  1611.      * 
  1612.      *
  1613.      *
  1614.      * @access  private
  1615.      * @var     array
  1616.      */
  1617.     var $_query = array(
  1618.         'condition'   => '', // the WHERE condition
  1619.         'group_by'    => '', // the GROUP BY condition
  1620.         'order_by'    => '', // the ORDER BY condition
  1621.         'having'      => '', // the HAVING condition
  1622.         'limit_start' => '', // the LIMIT condition
  1623.         'limit_count' => '', // the LIMIT condition
  1624.         'data_select' => '*', // the columns to be SELECTed
  1625.     );
  1626.         
  1627.     
  1628.   
  1629.  
  1630.     /**
  1631.      * Database result id (references global $_DB_DataObject[results]
  1632.      *
  1633.      * @access  private
  1634.      * @var     integer
  1635.      */
  1636.     var $_DB_resultid;
  1637.      
  1638.      /**
  1639.      * ResultFields - on the last call to fetch(), resultfields is sent here,
  1640.      * so we can clean up the memory.
  1641.      *
  1642.      * @access  public
  1643.      * @var     array
  1644.      */
  1645.     var $_resultFields = false; 
  1646.  
  1647.  
  1648.     /* ============================================================== */
  1649.     /*  Table definition layer (started of very private but 'came out'*/
  1650.     /* ============================================================== */
  1651.  
  1652.     /**
  1653.      * Autoload or manually load the table definitions
  1654.      *
  1655.      *
  1656.      * usage :
  1657.      * DB_DataObject::databaseStructure(  'databasename',
  1658.      *                                    parse_ini_file('mydb.ini',true), 
  1659.      *                                    parse_ini_file('mydb.link.ini',true)); 
  1660.      *
  1661.      * obviously you dont have to use ini files.. (just return array similar to ini files..)
  1662.      *  
  1663.      * It should append to the table structure array 
  1664.      *
  1665.      *     
  1666.      * @param optional string  name of database to assign / read
  1667.      * @param optional array   structure of database, and keys
  1668.      * @param optional array  table links
  1669.      *
  1670.      * @access public
  1671.      * @return true or PEAR:error on wrong paramenters.. or false if no file exists..
  1672.      *              or the array(tablename => array(column_name=>type)) if called with 1 argument.. (databasename)
  1673.      */
  1674.     function databaseStructure()
  1675.     {
  1676.  
  1677.         global $_DB_DATAOBJECT;
  1678.         
  1679.         // Assignment code 
  1680.         
  1681.         if ($args = func_get_args()) {
  1682.         
  1683.             if (count($args) == 1) {
  1684.                 
  1685.                 // this returns all the tables and their structure..
  1686.                 if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1687.                     $this->debug("Loading Generator as databaseStructure called with args",1);
  1688.                 }
  1689.                 
  1690.                 $x = new DB_DataObject;
  1691.                 $x->_database = $args[0];
  1692.                 $this->_connect();
  1693.                 $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1694.        
  1695.                 $tables = $DB->getListOf('tables');
  1696.                 require_once 'DB/DataObject/Generator.php';
  1697.                 foreach($tables as $table) {
  1698.                     $y = new DB_DataObject_Generator;
  1699.                     $y->fillTableSchema($x->_database,$table);
  1700.                 }
  1701.                 return $_DB_DATAOBJECT['INI'][$x->_database];            
  1702.             } else {
  1703.         
  1704.                 $_DB_DATAOBJECT['INI'][$args[0]] = isset($_DB_DATAOBJECT['INI'][$args[0]]) ?
  1705.                     $_DB_DATAOBJECT['INI'][$args[0]] + $args[1] : $args[1];
  1706.                 
  1707.                 if (isset($args[1])) {
  1708.                     $_DB_DATAOBJECT['LINKS'][$args[0]] = isset($_DB_DATAOBJECT['LINKS'][$args[0]]) ?
  1709.                         $_DB_DATAOBJECT['LINKS'][$args[0]] + $args[2] : $args[2];
  1710.                 }
  1711.                 return true;
  1712.             }
  1713.           
  1714.         }
  1715.         
  1716.         
  1717.         
  1718.         if (!$this->_database) {
  1719.             $this->_connect();
  1720.         }
  1721.         
  1722.         // loaded already?
  1723.         if (!empty($_DB_DATAOBJECT['INI'][$this->_database])) {
  1724.             
  1725.             // database loaded - but this is table is not available..
  1726.             if (
  1727.                     empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) 
  1728.                     && !empty($_DB_DATAOBJECT['CONFIG']['proxy'])
  1729.                 ) {
  1730.                 if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1731.                     $this->debug("Loading Generator to fetch Schema",1);
  1732.                 }
  1733.                 require_once 'DB/DataObject/Generator.php';
  1734.                 $x = new DB_DataObject_Generator;
  1735.                 $x->fillTableSchema($this->_database,$this->__table);
  1736.             }
  1737.             return true;
  1738.         }
  1739.         
  1740.         
  1741.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  1742.             DB_DataObject::_loadConfig();
  1743.         }
  1744.         
  1745.         // if you supply this with arguments, then it will take those
  1746.         // as the database and links array...
  1747.          
  1748.         $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ?
  1749.             array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") :
  1750.             array() ;
  1751.                  
  1752.         if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) {
  1753.             $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ?
  1754.                 $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] :
  1755.                 explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
  1756.         }
  1757.                     
  1758.          
  1759.         
  1760.         foreach ($schemas as $ini) {
  1761.              if (file_exists($ini) && is_file($ini)) {
  1762.                 $_DB_DATAOBJECT['INI'][$this->_database] = parse_ini_file($ini, true);
  1763.                 if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1764.                     $this->debug("Loaded ini file: $ini","databaseStructure",1);
  1765.                 }
  1766.             } else {
  1767.                 if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1768.                     $this->debug("Missing ini file: $ini","databaseStructure",1);
  1769.                 }
  1770.             }
  1771.              
  1772.         }
  1773.         // now have we loaded the structure.. 
  1774.         
  1775.         if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
  1776.             return true;
  1777.         }
  1778.         // - if not try building it..
  1779.         if (!empty($_DB_DATAOBJECT['CONFIG']['proxy'])) {
  1780.             require_once 'DB/DataObject/Generator.php';
  1781.             $x = new DB_DataObject_Generator;
  1782.             $x->fillTableSchema($this->_database,$this->__table);
  1783.             // should this fail!!!???
  1784.             return true;
  1785.         }
  1786.         $this->debug("Cant find database schema: {$this->_database}/{$this->__table} \n".
  1787.                     "in links file data: " . print_r($_DB_DATAOBJECT['INI'],true),"databaseStructure",5);
  1788.         // we have to die here!! - it causes chaos if we dont (including looping forever!)
  1789.         $this->raiseError( "Unable to load schema for database and table (turn debugging up to 5 for full error message)", DB_DATAOBJECT_ERROR_INVALIDARGS, PEAR_ERROR_DIE);
  1790.         return false;
  1791.         
  1792.          
  1793.     }
  1794.  
  1795.  
  1796.  
  1797.  
  1798.     /**
  1799.      * Return or assign the name of the current table
  1800.      *
  1801.      *
  1802.      * @param   string optinal table name to set
  1803.      * @access public
  1804.      * @return string The name of the current table
  1805.      */
  1806.     function tableName()
  1807.     {
  1808.         $args = func_get_args();
  1809.         if (count($args)) {
  1810.             $this->__table = $args[0];
  1811.         }
  1812.         return $this->__table;
  1813.     }
  1814.     
  1815.     /**
  1816.      * Return or assign the name of the current database
  1817.      *
  1818.      * @param   string optional database name to set
  1819.      * @access public
  1820.      * @return string The name of the current database
  1821.      */
  1822.     function database()
  1823.     {
  1824.         $args = func_get_args();
  1825.         if (count($args)) {
  1826.             $this->_database = $args[0];
  1827.         }
  1828.         return $this->_database;
  1829.     }
  1830.   
  1831.     /**
  1832.      * get/set an associative array of table columns
  1833.      *
  1834.      * @access public
  1835.      * @param  array key=>type array
  1836.      * @return array (associative)
  1837.      */
  1838.     function table()
  1839.     {
  1840.         
  1841.         // for temporary storage of database fields..
  1842.         // note this is not declared as we dont want to bloat the print_r output
  1843.         $args = func_get_args();
  1844.         if (count($args)) {
  1845.             $this->_database_fields = $args[0];
  1846.         }
  1847.         if (isset($this->_database_fields)) {
  1848.             return $this->_database_fields;
  1849.         }
  1850.         
  1851.         
  1852.         global $_DB_DATAOBJECT;
  1853.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  1854.             $this->_connect();
  1855.         }
  1856.         
  1857.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
  1858.             return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
  1859.         }
  1860.         
  1861.         $this->databaseStructure();
  1862.  
  1863.         
  1864.         $ret = array();
  1865.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
  1866.             $ret =  $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
  1867.         }
  1868.         
  1869.         return $ret;
  1870.     }
  1871.  
  1872.     /**
  1873.      * get/set an  array of table primary keys
  1874.      *
  1875.      * set usage: $do->keys('id','code');
  1876.      *
  1877.      * This is defined in the table definition if it gets it wrong,
  1878.      * or you do not want to use ini tables, you can override this.
  1879.      * @param  string optional set the key
  1880.      * @param  *   optional  set more keys
  1881.      * @access private
  1882.      * @return array
  1883.      */
  1884.     function keys()
  1885.     {
  1886.         // for temporary storage of database fields..
  1887.         // note this is not declared as we dont want to bloat the print_r output
  1888.         $args = func_get_args();
  1889.         if (count($args)) {
  1890.             $this->_database_keys = $args;
  1891.         }
  1892.         if (isset($this->_database_keys)) {
  1893.             return $this->_database_keys;
  1894.         }
  1895.         
  1896.         global $_DB_DATAOBJECT;
  1897.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  1898.             $this->_connect();
  1899.         }
  1900.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
  1901.             return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
  1902.         }
  1903.         $this->databaseStructure();
  1904.         
  1905.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
  1906.             return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
  1907.         }
  1908.         return array();
  1909.     }
  1910.     /**
  1911.      * get/set an  sequence key
  1912.      *
  1913.      * by default it returns the first key from keys()
  1914.      * set usage: $do->sequenceKey('id',true);
  1915.      *
  1916.      * override this to return array(false,false) if table has no real sequence key.
  1917.      *
  1918.      * @param  string  optional the key sequence/autoinc. key
  1919.      * @param  boolean optional use native increment. default false 
  1920.      * @param  false|string optional native sequence name
  1921.      * @access private
  1922.      * @return array (column,use_native,sequence_name)
  1923.      */
  1924.     function sequenceKey()
  1925.     {
  1926.         global $_DB_DATAOBJECT;
  1927.           
  1928.         // call setting
  1929.         if (!$this->_database) {
  1930.             $this->_connect();
  1931.         }
  1932.         
  1933.         if (!isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database])) {
  1934.             $_DB_DATAOBJECT['SEQUENCE'][$this->_database] = array();
  1935.         }
  1936.  
  1937.         
  1938.         $args = func_get_args();
  1939.         if (count($args)) {
  1940.             $args[1] = isset($args[1]) ? $args[1] : false;
  1941.             $args[2] = isset($args[2]) ? $args[2] : false;
  1942.             $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = $args;
  1943.         }
  1944.         if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table])) {
  1945.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table];
  1946.         }
  1947.         // end call setting (eg. $do->sequenceKeys(a,b,c); )
  1948.         
  1949.        
  1950.         
  1951.         
  1952.         $keys = $this->keys();
  1953.         if (!$keys) {
  1954.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] 
  1955.                 = array(false,false,false);
  1956.         }
  1957.  
  1958.  
  1959.         $table =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
  1960.             $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
  1961.        
  1962.         $dbtype    = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
  1963.         
  1964.         $usekey = $keys[0];
  1965.         
  1966.         
  1967.         
  1968.         $seqname = false;
  1969.         
  1970.         if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
  1971.             $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
  1972.             if (strpos($usekey,':') !== false) {
  1973.                 list($usekey,$seqname) = explode(':',$usekey);
  1974.             }
  1975.         }  
  1976.         
  1977.         
  1978.         // if the key is not an integer - then it's not a sequence or native
  1979.         if (empty($table[$usekey]) || !($table[$usekey] & DB_DATAOBJECT_INT)) {
  1980.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,false);
  1981.         }
  1982.         
  1983.         
  1984.         if (!empty($_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'])) {
  1985.             $ignore =  $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'];
  1986.             if (is_string($ignore) && (strtoupper($ignore) == 'ALL')) {
  1987.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
  1988.             }
  1989.             if (is_string($ignore)) {
  1990.                 $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'] = explode(',',$ignore);
  1991.             }
  1992.             if (in_array($this->__table,$ignore)) {
  1993.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
  1994.             }
  1995.         }
  1996.         
  1997.         
  1998.         $realkeys = $_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"];
  1999.         
  2000.         // if you are using an old ini file - go back to old behaviour...
  2001.         if (is_numeric($realkeys[$usekey])) {
  2002.             $realkeys[$usekey] = 'N';
  2003.         }
  2004.         
  2005.         // multiple unique primary keys without a native sequence...
  2006.         if (($realkeys[$usekey] == 'K') && (count($keys) > 1)) {
  2007.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
  2008.         }
  2009.         // use native sequence keys...
  2010.         // technically postgres native here...
  2011.         // we need to get the new improved tabledata sorted out first.
  2012.         
  2013.         if (    in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) && 
  2014.                 ($table[$usekey] & DB_DATAOBJECT_INT) && 
  2015.                 isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
  2016.                 ) {
  2017.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,true,$seqname);
  2018.         }
  2019.         // if not a native autoinc, and we have not assumed all primary keys are sequence
  2020.         if (($realkeys[$usekey] != 'N') && 
  2021.             !empty($_DB_DATAOBJECT['CONFIG']['dont_use_pear_sequences'])) {
  2022.             return array(false,false,false);
  2023.         }
  2024.         // I assume it's going to try and be a nextval DB sequence.. (not native)
  2025.         return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,false,$seqname);
  2026.     }
  2027.     
  2028.     
  2029.     
  2030.     /* =========================================================== */
  2031.     /*  Major Private Methods - the core part!              */
  2032.     /* =========================================================== */
  2033.  
  2034.  
  2035.     
  2036.     /**
  2037.      * clear the cache values for this class  - normally done on insert/update etc.
  2038.      *
  2039.      * @access private
  2040.      * @return void
  2041.      */
  2042.     function _clear_cache()
  2043.     {
  2044.         global $_DB_DATAOBJECT;
  2045.         
  2046.         $class = strtolower(get_class($this));
  2047.         
  2048.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2049.             $this->debug("Clearing Cache for ".$class,1);
  2050.         }
  2051.         
  2052.         if (!empty($_DB_DATAOBJECT['CACHE'][$class])) {
  2053.             unset($_DB_DATAOBJECT['CACHE'][$class]);
  2054.         }
  2055.     }
  2056.  
  2057.     
  2058.     /**
  2059.      * backend wrapper for quoting, as MDB2 and DB do it differently...
  2060.      *
  2061.      * @access private
  2062.      * @return string quoted
  2063.      */
  2064.     
  2065.     function _quote($str) 
  2066.     {
  2067.         global $_DB_DATAOBJECT;
  2068.         return (empty($_DB_DATAOBJECT['CONFIG']['db_driver']) || 
  2069.                 ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB'))
  2070.             ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quoteSmart($str)
  2071.             : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quote($str);
  2072.     }
  2073.     
  2074.     
  2075.     /**
  2076.      * connects to the database
  2077.      *
  2078.      *
  2079.      * TODO: tidy this up - This has grown to support a number of connection options like
  2080.      *  a) dynamic changing of ini file to change which database to connect to
  2081.      *  b) multi data via the table_{$table} = dsn ini option
  2082.      *  c) session based storage.
  2083.      *
  2084.      * @access private
  2085.      * @return true | PEAR::error
  2086.      */
  2087.     function _connect()
  2088.     {
  2089.         global $_DB_DATAOBJECT;
  2090.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2091.             $this->_loadConfig();
  2092.         }
  2093.         // Set database driver for reference 
  2094.         $db_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver'];
  2095.         // is it already connected ?
  2096.  
  2097.         if ($this->_database_dsn_md5 && !empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2098.             if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2099.                 return $this->raiseError(
  2100.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->message,
  2101.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE
  2102.                 );
  2103.                  
  2104.             }
  2105.  
  2106.             if (!$this->_database) {
  2107.                 $this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2108.                 $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase');
  2109.                 
  2110.                 $this->_database = ($db_driver != 'DB' && $hasGetDatabase)  
  2111.                         ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() 
  2112.                         : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2113.  
  2114.                 
  2115.                 
  2116.                 if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') 
  2117.                     && is_file($this->_database)) 
  2118.                 {
  2119.                     $this->_database = basename($this->_database);
  2120.                 }
  2121.                 
  2122.             }
  2123.             // theoretically we have a md5, it's listed in connections and it's not an error.
  2124.             // so everything is ok!
  2125.             return true;
  2126.             
  2127.         }
  2128.  
  2129.         // it's not currently connected!
  2130.         // try and work out what to use for the dsn !
  2131.  
  2132.         $options= &$_DB_DATAOBJECT['CONFIG'];
  2133.         $dsn = isset($this->_database_dsn) ? $this->_database_dsn : null;
  2134.         
  2135.         if (!$dsn) {
  2136.             if (!$this->_database) {
  2137.                 $this->_database = isset($options["table_{$this->__table}"]) ? $options["table_{$this->__table}"] : null;
  2138.             }
  2139.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2140.                 $this->debug("Checking for database database_{$this->_database} in options","CONNECT");
  2141.             }
  2142.             
  2143.             if ($this->_database && !empty($options["database_{$this->_database}"]))  {
  2144.                 
  2145.                 $dsn = $options["database_{$this->_database}"];
  2146.             } else if (!empty($options['database'])) {
  2147.                 $dsn = $options['database'];
  2148.             }
  2149.         }
  2150.         
  2151.         // if still no database...
  2152.         if (!$dsn) {
  2153.             return $this->raiseError(
  2154.                 "No database name / dsn found anywhere",
  2155.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG, PEAR_ERROR_DIE
  2156.             );
  2157.                  
  2158.         }
  2159.         
  2160.         
  2161.         if (is_string($dsn)) {
  2162.             $this->_database_dsn_md5 = md5($dsn);
  2163.         } else {
  2164.             /// support array based dsn's
  2165.             $this->_database_dsn_md5 = md5(serialize($dsn));
  2166.         }
  2167.  
  2168.         if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2169.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2170.                 $this->debug("USING CACHED CONNECTION", "CONNECT",3);
  2171.             }
  2172.             if (!$this->_database) {
  2173.  
  2174.                 $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase');
  2175.                 $this->_database = ($db_driver != 'DB' && $hasGetDatabase)  
  2176.                         ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() 
  2177.                         : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2178.                 
  2179.                 if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') 
  2180.                     && is_file($this->_database)) 
  2181.                 {
  2182.                     $this->_database = basename($this->_database);
  2183.                 }
  2184.             }
  2185.             return true;
  2186.         }
  2187.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2188.             $this->debug("NEW CONNECTION", "CONNECT",3);
  2189.             /* actualy make a connection */
  2190.             $this->debug(print_r($dsn,true) ." {$this->_database_dsn_md5}", "CONNECT",3);
  2191.         }
  2192.         
  2193.         // Note this is verbose deliberatly! 
  2194.         
  2195.         if ($db_driver == 'DB') {
  2196.             
  2197.             /* PEAR DB connect */
  2198.             
  2199.             // this allows the setings of compatibility on DB 
  2200.             $db_options = PEAR::getStaticProperty('DB','options');
  2201.             require_once 'DB.php';
  2202.             if ($db_options) {
  2203.                 $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn,$db_options);
  2204.             } else {
  2205.                 $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn);
  2206.             }
  2207.             
  2208.         } else {
  2209.             /* assumption is MDB2 */
  2210.             require_once 'MDB2.php';
  2211.             // this allows the setings of compatibility on MDB2 
  2212.             $db_options = PEAR::getStaticProperty('MDB2','options');
  2213.             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = MDB2::connect($dsn,$db_options);
  2214.             
  2215.         }
  2216.         
  2217.         
  2218.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2219.             $this->debug(serialize($_DB_DATAOBJECT['CONNECTIONS']), "CONNECT",5);
  2220.         }
  2221.         if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  2222.             $this->debug($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->toString(), "CONNECT FAILED",5);
  2223.             return $this->raiseError(
  2224.                     "Connect failed, turn on debugging to 5 see why",
  2225.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE
  2226.             );
  2227.  
  2228.         }
  2229.  
  2230.         if (!$this->_database) {
  2231.             $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase');
  2232.             
  2233.             $this->_database = ($db_driver != 'DB' && $hasGetDatabase)  
  2234.                     ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase() 
  2235.                     : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  2236.  
  2237.  
  2238.             if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite') 
  2239.                 && is_file($this->_database)) 
  2240.             {
  2241.                 $this->_database = basename($this->_database);
  2242.             }
  2243.         }
  2244.         
  2245.         // Oracle need to optimize for portibility - not sure exactly what this does though :)
  2246.         $c = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  2247.          
  2248.         return true;
  2249.     }
  2250.  
  2251.     /**
  2252.      * sends query to database - this is the private one that must work 
  2253.      *   - internal functions use this rather than $this->query()
  2254.      *
  2255.      * @param  string  $string
  2256.      * @access private
  2257.      * @return mixed none or PEAR_Error
  2258.      */
  2259.     function _query($string)
  2260.     {
  2261.         global $_DB_DATAOBJECT;
  2262.         $this->_connect();
  2263.         
  2264.  
  2265.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  2266.  
  2267.         $options = &$_DB_DATAOBJECT['CONFIG'];
  2268.         
  2269.         $_DB_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 
  2270.                     'DB':  $_DB_DATAOBJECT['CONFIG']['db_driver'];
  2271.         
  2272.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2273.             $this->debug($string,$log="QUERY");
  2274.             
  2275.         }
  2276.         
  2277.         if (strtoupper($string) == 'BEGIN') {
  2278.             if ($_DB_driver == 'DB') {
  2279.                 $DB->autoCommit(false);
  2280.             } else {
  2281.                 $DB->beginTransaction();
  2282.             }
  2283.             // db backend adds begin anyway from now on..
  2284.             return true;
  2285.         }
  2286.         if (strtoupper($string) == 'COMMIT') {
  2287.             $res = $DB->commit();
  2288.             if ($_DB_driver == 'DB') {
  2289.                 $DB->autoCommit(true);
  2290.             }
  2291.             return $res;
  2292.         }
  2293.         
  2294.         if (strtoupper($string) == 'ROLLBACK') {
  2295.             $DB->rollback();
  2296.             if ($_DB_driver == 'DB') {
  2297.                 $DB->autoCommit(true);
  2298.             }
  2299.             return true;
  2300.         }
  2301.         
  2302.  
  2303.         if (!empty($options['debug_ignore_updates']) &&
  2304.             (strtolower(substr(trim($string), 0, 6)) != 'select') &&
  2305.             (strtolower(substr(trim($string), 0, 4)) != 'show') &&
  2306.             (strtolower(substr(trim($string), 0, 8)) != 'describe')) {
  2307.  
  2308.             $this->debug('Disabling Update as you are in debug mode');
  2309.             return $this->raiseError("Disabling Update as you are in debug mode", null) ;
  2310.  
  2311.         }
  2312.         //if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 1) {
  2313.             // this will only work when PEAR:DB supports it.
  2314.             //$this->debug($DB->getAll('explain ' .$string,DB_DATAOBJECT_FETCHMODE_ASSOC), $log="sql",2);
  2315.         //}
  2316.         
  2317.         // some sim
  2318.         $t= explode(' ',microtime());
  2319.         $_DB_DATAOBJECT['QUERYENDTIME'] = $time = $t[0]+$t[1];
  2320.          
  2321.         
  2322.         if ($_DB_driver == 'DB') {
  2323.             $result = $DB->query($string);
  2324.         } else {
  2325.             switch (strtolower(substr(trim($string),0,6))) {
  2326.             
  2327.                 case 'insert':
  2328.                 case 'update':
  2329.                 case 'delete':
  2330.                     $result = $DB->exec($string);
  2331.                     break;
  2332.                     
  2333.                 default:
  2334.                     $result = $DB->query($string);
  2335.                     break;
  2336.             }
  2337.         }
  2338.         
  2339.        
  2340.  
  2341.         if (is_a($result,'PEAR_Error')) {
  2342.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { 
  2343.                 $this->debug($result->toString(), "Query Error",1 );
  2344.             }
  2345.             return $this->raiseError($result);
  2346.         }
  2347.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2348.             $t= explode(' ',microtime());
  2349.             $_DB_DATAOBJECT['QUERYENDTIME'] = $t[0]+$t[1];
  2350.             $this->debug('QUERY DONE IN  '.($t[0]+$t[1]-$time)." seconds", 'query',1);
  2351.         }
  2352.         switch (strtolower(substr(trim($string),0,6))) {
  2353.             case 'insert':
  2354.             case 'update':
  2355.             case 'delete':
  2356.                 if ($_DB_driver == 'DB') {
  2357.                     // pear DB specific
  2358.                     return $DB->affectedRows(); 
  2359.                 }
  2360.                 return $result;
  2361.         }
  2362.         if (is_object($result)) {
  2363.             // lets hope that copying the result object is OK!
  2364.             
  2365.             $_DB_resultid  = $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ']++;
  2366.             $_DB_DATAOBJECT['RESULTS'][$_DB_resultid] = $result; 
  2367.             $this->_DB_resultid = $_DB_resultid;
  2368.         }
  2369.         $this->N = 0;
  2370.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2371.             $this->debug(serialize($result), 'RESULT',5);
  2372.         }
  2373.         if (method_exists($result, 'numrows')) {
  2374.             if ($_DB_driver == 'DB') {
  2375.                 $DB->expectError(DB_ERROR_UNSUPPORTED);
  2376.             } else {
  2377.                 $DB->expectError(MDB2_ERROR_UNSUPPORTED);
  2378.             }
  2379.             $this->N = $result->numrows();
  2380.             if (is_a($this->N,'PEAR_Error')) {
  2381.                 $this->N = true;
  2382.             }
  2383.             $DB->popExpect();
  2384.         }
  2385.     }
  2386.  
  2387.     /**
  2388.      * Builds the WHERE based on the values of of this object
  2389.      *
  2390.      * @param   mixed   $keys
  2391.      * @param   array   $filter (used by update to only uses keys in this filter list).
  2392.      * @param   array   $negative_filter (used by delete to prevent deleting using the keys mentioned..)
  2393.      * @access  private
  2394.      * @return  string
  2395.      */
  2396.     function _build_condition($keys, $filter = array(),$negative_filter=array())
  2397.     {
  2398.         global $_DB_DATAOBJECT;
  2399.         $this->_connect();
  2400.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  2401.        
  2402.         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  2403.         // if we dont have query vars.. - reset them.
  2404.         if (!isset($this->_query)) {
  2405.             $x = new DB_DataObject;
  2406.             $this->_query= $x->_query;
  2407.         }
  2408.  
  2409.         foreach($keys as $k => $v) {
  2410.             // index keys is an indexed array
  2411.             /* these filter checks are a bit suspicious..
  2412.                 - need to check that update really wants to work this way */
  2413.  
  2414.             if ($filter) {
  2415.                 if (!in_array($k, $filter)) {
  2416.                     continue;
  2417.                 }
  2418.             }
  2419.             if ($negative_filter) {
  2420.                 if (in_array($k, $negative_filter)) {
  2421.                     continue;
  2422.                 }
  2423.             }
  2424.             if (!isset($this->$k)) {
  2425.                 continue;
  2426.             }
  2427.             
  2428.             $kSql = $quoteIdentifiers 
  2429.                 ? ( $DB->quoteIdentifier($this->__table) . '.' . $DB->quoteIdentifier($k) )  
  2430.                 : "{$this->__table}.{$k}";
  2431.              
  2432.              
  2433.             
  2434.             if (is_a($this->$k,'DB_DataObject_Cast')) {
  2435.                 $dbtype = $DB->dsn["phptype"];
  2436.                 $value = $this->$k->toString($v,$DB);
  2437.                 if (PEAR::isError($value)) {
  2438.                     $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
  2439.                     return false;
  2440.                 }
  2441.                 if ((strtolower($value) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
  2442.                     $this->whereAdd(" $kSql IS NULL");
  2443.                     continue;
  2444.                 }
  2445.                 $this->whereAdd(" $kSql = $value");
  2446.                 continue;
  2447.             }
  2448.             
  2449.             if ((strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
  2450.                 $this->whereAdd(" $kSql  IS NULL");
  2451.                 continue;
  2452.             }
  2453.             
  2454.  
  2455.             if ($v & DB_DATAOBJECT_STR) {
  2456.                 $this->whereAdd(" $kSql  = " . $this->_quote((string) (
  2457.                         ($v & DB_DATAOBJECT_BOOL) ? 
  2458.                             // this is thanks to the braindead idea of postgres to 
  2459.                             // use t/f for boolean.
  2460.                             (($this->$k == 'f') ? 0 : (int)(bool) $this->$k) :  
  2461.                             $this->$k
  2462.                     )) );
  2463.                 continue;
  2464.             }
  2465.             if (is_numeric($this->$k)) {
  2466.                 $this->whereAdd(" $kSql = {$this->$k}");
  2467.                 continue;
  2468.             }
  2469.             /* this is probably an error condition! */
  2470.             $this->whereAdd(" $kSql = ".intval($this->$k));
  2471.         }
  2472.     }
  2473.  
  2474.     /**
  2475.      * autoload Class relating to a table
  2476.      * (depreciated - use ::factory)
  2477.      *
  2478.      * @param  string  $table  table
  2479.      * @access private
  2480.      * @return string classname on Success
  2481.      */
  2482.     function staticAutoloadTable($table)
  2483.     {
  2484.         global $_DB_DATAOBJECT;
  2485.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2486.             DB_DataObject::_loadConfig();
  2487.         }
  2488.         $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
  2489.             $_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
  2490.         $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
  2491.         
  2492.         $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
  2493.         $class = $ce ? $class  : DB_DataObject::_autoloadClass($class);
  2494.         return $class;
  2495.     }
  2496.     
  2497.     
  2498.      /**
  2499.      * classic factory method for loading a table class
  2500.      * usage: $do = DB_DataObject::factory('person')
  2501.      * WARNING - this may emit a include error if the file does not exist..
  2502.      * use @ to silence it (if you are sure it is acceptable)
  2503.      * eg. $do = @DB_DataObject::factory('person')
  2504.      *
  2505.      * table name will eventually be databasename/table
  2506.      * - and allow modular dataobjects to be written..
  2507.      * (this also helps proxy creation)
  2508.      *
  2509.      *
  2510.      * @param  string  $table  tablename (use blank to create a new instance of the same class.)
  2511.      * @access private
  2512.      * @return DataObject|PEAR_Error 
  2513.      */
  2514.     
  2515.     
  2516.  
  2517.     function factory($table = '') {
  2518.         global $_DB_DATAOBJECT;
  2519.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2520.             DB_DataObject::_loadConfig();
  2521.         }
  2522.         
  2523.         if ($table === '') {
  2524.             if (is_a($this,'DB_DataObject') && strlen($this->__table)) {
  2525.                 $table = $this->__table;
  2526.             } else {
  2527.                 return DB_DataObject::raiseError(
  2528.                     "factory did not recieve a table name",
  2529.                     DB_DATAOBJECT_ERROR_INVALIDARGS);
  2530.             }
  2531.         }
  2532.         
  2533.         
  2534.         $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
  2535.             $_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
  2536.         $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
  2537.         
  2538.         $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
  2539.         $class = $ce ? $class  : DB_DataObject::_autoloadClass($class);
  2540.         
  2541.         // proxy = full|light
  2542.         if (!$class && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) { 
  2543.             $proxyMethod = 'getProxy'.$_DB_DATAOBJECT['CONFIG']['proxy'];
  2544.             
  2545.             require_once 'DB/DataObject/Generator.php';
  2546.             $d = new DB_DataObject;
  2547.            
  2548.             $d->__table = $table;
  2549.             if (is_a($ret = $d->_connect(), 'PEAR_Error')) {
  2550.                 return $ret;
  2551.             }
  2552.             
  2553.             $x = new DB_DataObject_Generator;
  2554.             return $x->$proxyMethod( $d->_database, $table);
  2555.         }
  2556.         
  2557.         if (!$class) {
  2558.             return DB_DataObject::raiseError(
  2559.                 "factory could not find class $class from $table",
  2560.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2561.         }
  2562.  
  2563.         return new $class;
  2564.     }
  2565.     /**
  2566.      * autoload Class
  2567.      *
  2568.      * @param  string  $class  Class
  2569.      * @access private
  2570.      * @return string classname on Success
  2571.      */
  2572.     function _autoloadClass($class)
  2573.     {
  2574.         global $_DB_DATAOBJECT;
  2575.         
  2576.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2577.             DB_DataObject::_loadConfig();
  2578.         }
  2579.         $class_prefix = empty($_DB_DATAOBJECT['CONFIG']['class_prefix']) ? 
  2580.                 '' : $_DB_DATAOBJECT['CONFIG']['class_prefix'];
  2581.                 
  2582.         $table   = substr($class,strlen($class_prefix));
  2583.  
  2584.         // only include the file if it exists - and barf badly if it has parse errors :)
  2585.         if (!empty($_DB_DATAOBJECT['CONFIG']['proxy']) || empty($_DB_DATAOBJECT['CONFIG']['class_location'])) {
  2586.             return false;
  2587.         }
  2588.         
  2589.         
  2590.         if (strpos($_DB_DATAOBJECT['CONFIG']['class_location'],'%s') !== false) {
  2591.             $file = sprintf($_DB_DATAOBJECT['CONFIG']['class_location'], preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)));
  2592.         } else {
  2593.             $file = $_DB_DATAOBJECT['CONFIG']['class_location'].'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php";
  2594.         }
  2595.         
  2596.         if (!file_exists($file)) {
  2597.             $found = false;
  2598.             foreach(explode(PATH_SEPARATOR, ini_get('include_path')) as $p) {
  2599.                 if (file_exists("$p/$file")) {
  2600.                     $file = "$p/$file";
  2601.                     $found = true;
  2602.                     break;
  2603.                 }
  2604.             }
  2605.             if (!$found) {
  2606.                 DB_DataObject::raiseError(
  2607.                     "autoload:Could not find class {$class} using class_location value", 
  2608.                     DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2609.                 return false;
  2610.             }
  2611.         }
  2612.         
  2613.         include_once $file;
  2614.         
  2615.         
  2616.         $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
  2617.         
  2618.         if (!$ce) {
  2619.             DB_DataObject::raiseError(
  2620.                 "autoload:Could not autoload {$class}", 
  2621.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2622.             return false;
  2623.         }
  2624.         return $class;
  2625.     }
  2626.     
  2627.     
  2628.     
  2629.     /**
  2630.      * Have the links been loaded?
  2631.      * if they have it contains a array of those variables.
  2632.      *
  2633.      * @access  private
  2634.      * @var     boolean | array
  2635.      */
  2636.     var $_link_loaded = false;
  2637.     
  2638.     /**
  2639.     * Get the links associate array  as defined by the links.ini file.
  2640.     * 
  2641.     *
  2642.     * Experimental... - 
  2643.     * Should look a bit like
  2644.     *       [local_col_name] => "related_tablename:related_col_name"
  2645.     * 
  2646.     * 
  2647.     * @return   array|null    
  2648.     *           array       = if there are links defined for this table.
  2649.     *           empty array - if there is a links.ini file, but no links on this table
  2650.     *           null        - if no links.ini exists for this database (hence try auto_links).
  2651.     * @access   public
  2652.     * @see      DB_DataObject::getLinks(), DB_DataObject::getLink()
  2653.     */
  2654.     
  2655.     function links()
  2656.     {
  2657.         global $_DB_DATAOBJECT;
  2658.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2659.             $this->_loadConfig();
  2660.         }
  2661.         // have to connect.. -> otherwise things break later.
  2662.         $this->_connect();
  2663.         
  2664.         if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
  2665.             return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
  2666.         }
  2667.         
  2668.         
  2669.         
  2670.         
  2671.         
  2672.         // attempt to load links file here..
  2673.         
  2674.         if (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) {
  2675.             $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ?
  2676.                 array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") :
  2677.                 array() ;
  2678.                      
  2679.             if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) {
  2680.                 $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ?
  2681.                     $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] :
  2682.                     explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
  2683.             }
  2684.                         
  2685.              
  2686.             
  2687.             foreach ($schemas as $ini) {
  2688.                 
  2689.                 $links =
  2690.                     isset($_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"]) ?
  2691.                         $_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"] :
  2692.                         str_replace('.ini','.links.ini',$ini);
  2693.         
  2694.                 if (empty($_DB_DATAOBJECT['LINKS'][$this->_database]) && file_exists($links) && is_file($links)) {
  2695.                     /* not sure why $links = ... here  - TODO check if that works */
  2696.                     $_DB_DATAOBJECT['LINKS'][$this->_database] = parse_ini_file($links, true);
  2697.                     if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2698.                         $this->debug("Loaded links.ini file: $links","links",1);
  2699.                     }
  2700.                 } else {
  2701.                     if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2702.                         $this->debug("Missing links.ini file: $links","links",1);
  2703.                     }
  2704.                 }
  2705.             }
  2706.         }
  2707.         
  2708.         
  2709.         // if there is no link data at all on the file!
  2710.         // we return null.
  2711.         if (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) {
  2712.             return null;
  2713.         }
  2714.         
  2715.         if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
  2716.             return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
  2717.         }
  2718.         
  2719.         return array();
  2720.     }
  2721.     /**
  2722.      * load related objects
  2723.      *
  2724.      * There are two ways to use this, one is to set up a <dbname>.links.ini file
  2725.      * into a static property named <dbname>.links and specifies the table joins,
  2726.      * the other highly dependent on naming columns 'correctly' :)
  2727.      * using colname = xxxxx_yyyyyy
  2728.      * xxxxxx = related table; (yyyyy = user defined..)
  2729.      * looks up table xxxxx, for value id=$this->xxxxx
  2730.      * stores it in $this->_xxxxx_yyyyy
  2731.      * you can change what object vars the links are stored in by 
  2732.      * changeing the format parameter
  2733.      *
  2734.      *
  2735.      * @param  string format (default _%s) where %s is the table name.
  2736.      * @author Tim White <tim@cyface.com>
  2737.      * @access public
  2738.      * @return boolean , true on success
  2739.      */
  2740.     function getLinks($format = '_%s')
  2741.     {
  2742.          
  2743.         // get table will load the options.
  2744.         if ($this->_link_loaded) {
  2745.             return true;
  2746.         }
  2747.         $this->_link_loaded = false;
  2748.         $cols  = $this->table();
  2749.         $links = $this->links();
  2750.          
  2751.         $loaded = array();
  2752.         
  2753.         if ($links) {   
  2754.             foreach($links as $key => $match) {
  2755.                 list($table,$link) = explode(':', $match);
  2756.                 $k = sprintf($format, str_replace('.', '_', $key));
  2757.                 // makes sure that '.' is the end of the key;
  2758.                 if ($p = strpos($key,'.')) {
  2759.                       $key = substr($key, 0, $p);
  2760.                 }
  2761.                 
  2762.                 $this->$k = $this->getLink($key, $table, $link);
  2763.                 if (is_object($this->$k)) {
  2764.                     $loaded[] = $k; 
  2765.                 }
  2766.             }
  2767.             $this->_link_loaded = $loaded;
  2768.             return true;
  2769.         }
  2770.         // this is the autonaming stuff..
  2771.         // it sends the column name down to getLink and lets that sort it out..
  2772.         // if there is a links file then it is not used!
  2773.         // IT IS DEPRECIATED!!!! - USE 
  2774.         if (!is_null($links)) {    
  2775.             return false;
  2776.         }
  2777.         
  2778.         
  2779.         foreach (array_keys($cols) as $key) {
  2780.             if (!($p = strpos($key, '_'))) {
  2781.                 continue;
  2782.             }
  2783.             // does the table exist.
  2784.             $k =sprintf($format, $key);
  2785.             $this->$k = $this->getLink($key);
  2786.             if (is_object($this->$k)) {
  2787.                 $loaded[] = $k; 
  2788.             }
  2789.         }
  2790.         $this->_link_loaded = $loaded;
  2791.         return true;
  2792.     }
  2793.  
  2794.     /**
  2795.      * return name from related object
  2796.      *
  2797.      * There are two ways to use this, one is to set up a <dbname>.links.ini file
  2798.      * into a static property named <dbname>.links and specifies the table joins,
  2799.      * the other is highly dependant on naming columns 'correctly' :)
  2800.      *
  2801.      * NOTE: the naming convention is depreciated!!! - use links.ini
  2802.      *
  2803.      * using colname = xxxxx_yyyyyy
  2804.      * xxxxxx = related table; (yyyyy = user defined..)
  2805.      * looks up table xxxxx, for value id=$this->xxxxx
  2806.      * stores it in $this->_xxxxx_yyyyy
  2807.      *
  2808.      * you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName')
  2809.      *
  2810.      *
  2811.      * @param string $row    either row or row.xxxxx
  2812.      * @param string $table  name of table to look up value in
  2813.      * @param string $link   name of column in other table to match
  2814.      * @author Tim White <tim@cyface.com>
  2815.      * @access public
  2816.      * @return mixed object on success
  2817.      */
  2818.     function &getLink($row, $table = null, $link = false)
  2819.     {
  2820.         
  2821.         
  2822.         // GUESS THE LINKED TABLE.. (if found - recursevly call self)
  2823.         
  2824.         if ($table === null) {
  2825.             $links = $this->links();
  2826.             
  2827.             if (is_array($links)) {
  2828.             
  2829.                 if ($links[$row]) {
  2830.                     list($table,$link) = explode(':', $links[$row]);
  2831.                     if ($p = strpos($row,".")) {
  2832.                         $row = substr($row,0,$p);
  2833.                     }
  2834.                     $r = &$this->getLink($row,$table,$link);
  2835.                     return $r;
  2836.                 } 
  2837.                 
  2838.                 $this->raiseError(
  2839.                     "getLink: $row is not defined as a link (normally this is ok)", 
  2840.                     DB_DATAOBJECT_ERROR_NODATA);
  2841.                     
  2842.                 $r = false;
  2843.                 return $r;// technically a possible error condition?
  2844.  
  2845.             }  
  2846.             // use the old _ method - this shouldnt happen if called via getLinks()
  2847.             if (!($p = strpos($row, '_'))) {
  2848.                 $r = null;
  2849.                 return $r; 
  2850.             }
  2851.             $table = substr($row, 0, $p);
  2852.             $r = &$this->getLink($row, $table);
  2853.             return $r;
  2854.  
  2855.         }
  2856.         
  2857.         
  2858.         
  2859.         if (!isset($this->$row)) {
  2860.             $this->raiseError("getLink: row not set $row", DB_DATAOBJECT_ERROR_NODATA);
  2861.             $r = false;
  2862.             return $r;
  2863.         }
  2864.         
  2865.         // check to see if we know anything about this table..
  2866.         
  2867.         $obj = $this->factory($table);
  2868.         
  2869.         if (!is_a($obj,'DB_DataObject')) {
  2870.             $this->raiseError(
  2871.                 "getLink:Could not find class for row $row, table $table", 
  2872.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2873.             $r = false;
  2874.             return $r;
  2875.         }
  2876.         if ($link) {
  2877.             if ($obj->get($link, $this->$row)) {
  2878.                 return $obj;
  2879.             } 
  2880.             $r = false;
  2881.             return $r;
  2882.         }
  2883.         
  2884.         if ($obj->get($this->$row)) {
  2885.             return $obj;
  2886.         }
  2887.         $r = false;
  2888.         return $r;
  2889.     }
  2890.  
  2891.     /**
  2892.      * IS THIS SUPPORTED/USED ANYMORE???? 
  2893.      *return a list of options for a linked table
  2894.      *
  2895.      * This is highly dependant on naming columns 'correctly' :)
  2896.      * using colname = xxxxx_yyyyyy
  2897.      * xxxxxx = related table; (yyyyy = user defined..)
  2898.      * looks up table xxxxx, for value id=$this->xxxxx
  2899.      * stores it in $this->_xxxxx_yyyyy
  2900.      *
  2901.      * @access public
  2902.      * @return array of results (empty array on failure)
  2903.      */
  2904.     function &getLinkArray($row, $table = null)
  2905.     {
  2906.         
  2907.         $ret = array();
  2908.         if (!$table) {
  2909.             $links = $this->links();
  2910.             
  2911.             if (is_array($links)) {
  2912.                 if (!isset($links[$row])) {
  2913.                     // failed..
  2914.                     return $ret;
  2915.                 }
  2916.                 list($table,$link) = explode(':',$links[$row]);
  2917.             } else {
  2918.                 if (!($p = strpos($row,'_'))) {
  2919.                     return $ret;
  2920.                 }
  2921.                 $table = substr($row,0,$p);
  2922.             }
  2923.         }
  2924.         
  2925.         $c  = $this->factory($table);
  2926.         
  2927.         if (!is_a($c,'DB_DataObject')) {
  2928.             $this->raiseError(
  2929.                 "getLinkArray:Could not find class for row $row, table $table", 
  2930.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG
  2931.             );
  2932.             return $ret;
  2933.         }
  2934.  
  2935.         // if the user defined method list exists - use it...
  2936.         if (method_exists($c, 'listFind')) {
  2937.             $c->listFind($this->id);
  2938.         } else {
  2939.             $c->find();
  2940.         }
  2941.         while ($c->fetch()) {
  2942.             $ret[] = $c;
  2943.         }
  2944.         return $ret;
  2945.     }
  2946.  
  2947.     /**
  2948.      * The JOIN condition
  2949.      *
  2950.      * @access  private
  2951.      * @var     string
  2952.      */
  2953.     var $_join = '';
  2954.  
  2955.     /**
  2956.      * joinAdd - adds another dataobject to this, building a joined query.
  2957.      *
  2958.      * example (requires links.ini to be set up correctly)
  2959.      * // get all the images for product 24
  2960.      * $i = new DataObject_Image();
  2961.      * $pi = new DataObjects_Product_image();
  2962.      * $pi->product_id = 24; // set the product id to 24
  2963.      * $i->joinAdd($pi); // add the product_image connectoin
  2964.      * $i->find();
  2965.      * while ($i->fetch()) {
  2966.      *     // do stuff
  2967.      * }
  2968.      * // an example with 2 joins
  2969.      * // get all the images linked with products or productgroups
  2970.      * $i = new DataObject_Image();
  2971.      * $pi = new DataObject_Product_image();
  2972.      * $pgi = new DataObject_Productgroup_image();
  2973.      * $i->joinAdd($pi);
  2974.      * $i->joinAdd($pgi);
  2975.      * $i->find();
  2976.      * while ($i->fetch()) {
  2977.      *     // do stuff
  2978.      * }
  2979.      *
  2980.      *
  2981.      * @param    optional $obj       object |array    the joining object (no value resets the join)
  2982.      *                                          If you use an array here it should be in the format:
  2983.      *                                          array('local_column','remotetable:remote_column');
  2984.      *                                          if remotetable does not have a definition, you should
  2985.      *                                          use @ to hide the include error message..
  2986.      *                                      
  2987.      *
  2988.      * @param    optional $joinType  string     'LEFT'|'INNER'|'RIGHT'|'' Inner is default, '' indicates 
  2989.      *                                          just select ... from a,b,c with no join and 
  2990.      *                                          links are added as where items.
  2991.      *
  2992.      * @param    optional $joinAs    string     if you want to select the table as anther name
  2993.      *                                          useful when you want to select multiple columsn
  2994.      *                                          from a secondary table.
  2995.      
  2996.      * @param    optional $joinCol   string     The column on This objects table to match (needed
  2997.      *                                          if this table links to the child object in 
  2998.      *                                          multiple places eg.
  2999.      *                                          user->friend (is a link to another user)
  3000.      *                                          user->mother (is a link to another user..)
  3001.      *
  3002.      * @return   none
  3003.      * @access   public
  3004.      * @author   Stijn de Reede      <sjr@gmx.co.uk>
  3005.      */
  3006.     function joinAdd($obj = false, $joinType='INNER', $joinAs=false, $joinCol=false)
  3007.     {
  3008.         global $_DB_DATAOBJECT;
  3009.         if ($obj === false) {
  3010.             $this->_join = '';
  3011.             return;
  3012.         }
  3013.         
  3014.         // support for array as first argument 
  3015.         // this assumes that you dont have a links.ini for the specified table.
  3016.         // and it doesnt exist as am extended dataobject!! - experimental.
  3017.         
  3018.         $ofield = false; // object field
  3019.         $tfield = false; // this field
  3020.         $toTable = false;
  3021.         if (is_array($obj)) {
  3022.             $tfield = $obj[0];
  3023.             list($toTable,$ofield) = explode(':',$obj[1]);
  3024.             $obj = DB_DataObject::factory($toTable);
  3025.             
  3026.             if (!$obj || is_a($obj,'PEAR_Error')) {
  3027.                 $obj = new DB_DataObject;
  3028.                 $obj->__table = $toTable;
  3029.             }
  3030.             $obj->_connect();
  3031.             // set the table items to nothing.. - eg. do not try and match
  3032.             // things in the child table...???
  3033.             $items = array();
  3034.         }
  3035.         
  3036.         if (!is_object($obj) || !is_a($obj,'DB_DataObject')) {
  3037.             return $this->raiseError("joinAdd: called without an object", DB_DATAOBJECT_ERROR_NODATA,PEAR_ERROR_DIE);
  3038.         }
  3039.         /*  make sure $this->_database is set.  */
  3040.         $this->_connect();
  3041.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  3042.        
  3043.  
  3044.         
  3045.         
  3046.          /* look up the links for obj table */
  3047.         //print_r($obj->links());
  3048.         if (!$ofield && ($olinks = $obj->links())) {
  3049.             
  3050.             foreach ($olinks as $k => $v) {
  3051.                 /* link contains {this column} = {linked table}:{linked column} */
  3052.                 $ar = explode(':', $v);
  3053.                 if ($ar[0] == $this->__table) {
  3054.                     
  3055.                     // you have explictly specified the column
  3056.                     // and the col is listed here..
  3057.                     // not sure if 1:1 table could cause probs here..
  3058.                     
  3059.                     if ($joinCol !== false) {
  3060.                         $this->raiseError( 
  3061.                             "joinAdd: You cannot target a join column in the " .
  3062.                             "'link from' table ({$obj->__table}). " . 
  3063.                             "Either remove the fourth argument to joinAdd() ".
  3064.                             "({$joinCol}), or alter your links.ini file.",
  3065.                             DB_DATAOBJECT_ERROR_NODATA);
  3066.                         return false;
  3067.                     }
  3068.                 
  3069.                     $ofield = $k;
  3070.                     $tfield = $ar[1];
  3071.                     break;
  3072.                 }
  3073.             }
  3074.         }
  3075.  
  3076.         /* otherwise see if there are any links from this table to the obj. */
  3077.         //print_r($this->links());
  3078.         if (($ofield === false) && ($links = $this->links())) {
  3079.             foreach ($links as $k => $v) {
  3080.                 /* link contains {this column} = {linked table}:{linked column} */
  3081.                 $ar = explode(':', $v);
  3082.                 if ($ar[0] == $obj->__table) {
  3083.                     if ($joinCol !== false) {
  3084.                         if ($k == $joinCol) {
  3085.                             $tfield = $k;
  3086.                             $ofield = $ar[1];
  3087.                             break;
  3088.                         } else {
  3089.                             continue;
  3090.                         }
  3091.                     } else {
  3092.                         $tfield = $k;
  3093.                         $ofield = $ar[1];
  3094.                         break;
  3095.                     }
  3096.                 }
  3097.             }
  3098.         }
  3099.         
  3100.         /* did I find a conneciton between them? */
  3101.  
  3102.         if ($ofield === false) {
  3103.             $this->raiseError(
  3104.                 "joinAdd: {$obj->__table} has no link with {$this->__table}",
  3105.                 DB_DATAOBJECT_ERROR_NODATA);
  3106.             return false;
  3107.         }
  3108.         $joinType = strtoupper($joinType);
  3109.         
  3110.         // we default to joining as the same name (this is remvoed later..)
  3111.         
  3112.         if ($joinAs === false) {
  3113.             $joinAs = $obj->__table;
  3114.         }
  3115.         
  3116.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  3117.         
  3118.         // not sure  how portable adding database prefixes is..
  3119.         $objTable = $quoteIdentifiers ? 
  3120.                 $DB->quoteIdentifier($obj->__table) : 
  3121.                  $obj->__table ;
  3122.                 
  3123.          
  3124.         // as far as we know only mysql supports database prefixes..
  3125.         // prefixing the database name is now the default behaviour,
  3126.         // as it enables joining mutiple columns from multiple databases...
  3127.         if (    
  3128.                 in_array($DB->dsn['phptype'],array('mysql','mysqli')) &&
  3129.                 strlen($obj->_database)
  3130.             ) 
  3131.         {
  3132.             // prefix database (quoted if neccessary..)
  3133.             $objTable = ($quoteIdentifiers
  3134.                          ? $DB->quoteIdentifier($obj->_database)
  3135.                          : $obj->_database)
  3136.                     . '.' . $objTable;
  3137.         }
  3138.          
  3139.         
  3140.         
  3141.         
  3142.         // nested (join of joined objects..)
  3143.         $appendJoin = '';
  3144.         if ($obj->_join) {
  3145.             // postgres allows nested queries, with ()'s
  3146.             // not sure what the results are with other databases..
  3147.             // may be unpredictable..
  3148.             if (in_array($DB->dsn["phptype"],array('pgsql'))) {
  3149.                 $objTable = "($objTable {$obj->_join})";
  3150.             } else {
  3151.                 $appendJoin = $obj->_join;
  3152.             }
  3153.         }
  3154.         
  3155.         
  3156.         $table = $this->__table;
  3157.         
  3158.         if ($quoteIdentifiers) {
  3159.             $joinAs   = $DB->quoteIdentifier($joinAs);
  3160.             $table    = $DB->quoteIdentifier($table);     
  3161.             $ofield   = $DB->quoteIdentifier($ofield);    
  3162.             $tfield   = $DB->quoteIdentifier($tfield);    
  3163.         }
  3164.         // add database prefix if they are different databases
  3165.        
  3166.         
  3167.         $fullJoinAs = '';
  3168.         $addJoinAs  = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->__table) : $obj->__table) != $joinAs;
  3169.         if ($addJoinAs) {
  3170.             // join table a AS b - is only supported by a few databases and is probably not needed
  3171.             // , however since it makes the whole Statement alot clearer we are leaving it in
  3172.             // for those databases.
  3173.             $fullJoinAs = in_array($DB->dsn["phptype"],array('mysql','mysqli','pgsql')) ? "AS {$joinAs}" :  $joinAs;
  3174.         } else {
  3175.             // if 
  3176.             if (
  3177.                     in_array($DB->dsn['phptype'],array('mysql','mysqli')) &&
  3178.                     strlen($obj->_database)
  3179.                 ) 
  3180.             {
  3181.                 $joinAs = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->_database) : $obj->_database) . '.' . $joinAs;
  3182.             }
  3183.         }
  3184.         
  3185.         
  3186.         switch ($joinType) {
  3187.             case 'INNER':
  3188.             case 'LEFT': 
  3189.             case 'RIGHT': // others??? .. cross, left outer, right outer, natural..?
  3190.                 $this->_join .= "\n {$joinType} JOIN {$objTable}  {$fullJoinAs}".
  3191.                                 " ON {$joinAs}.{$ofield}={$table}.{$tfield} {$appendJoin} ";
  3192.                 break;
  3193.             case '': // this is just a standard multitable select..
  3194.                 $this->_join .= "\n , {$objTable} {$fullJoinAs} {$appendJoin}";
  3195.                 $this->whereAdd("{$joinAs}.{$ofield}={$table}.{$tfield}");
  3196.         }
  3197.          
  3198.         // if obj only a dataobject - eg. no extended class has been defined..
  3199.         // it obvioulsy cant work out what child elements might exist...
  3200.         // untill we get on the fly querying of tables..
  3201.         if ( strtolower(get_class($obj)) == 'db_dataobject') {
  3202.             return true;
  3203.         }
  3204.          
  3205.         /* now add where conditions for anything that is set in the object */
  3206.     
  3207.     
  3208.     
  3209.         $items = $obj->table();
  3210.         // will return an array if no items..
  3211.         
  3212.         // only fail if we where expecting it to work (eg. not joined on a array)
  3213.         
  3214.         
  3215.         
  3216.         if (!$items) {
  3217.             $this->raiseError(
  3218.                 "joinAdd: No table definition for {$obj->__table}", 
  3219.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  3220.             return false;
  3221.         }
  3222.  
  3223.         foreach($items as $k => $v) {
  3224.             if (!isset($obj->$k)) {
  3225.                 continue;
  3226.             }
  3227.             
  3228.             $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
  3229.             
  3230.             
  3231.             if ($v & DB_DATAOBJECT_STR) {
  3232.                 $this->whereAdd("{$joinAs}.{$kSql} = " . $this->_quote((string) (
  3233.                         ($v & DB_DATAOBJECT_BOOL) ? 
  3234.                             // this is thanks to the braindead idea of postgres to 
  3235.                             // use t/f for boolean.
  3236.                             (($obj->$k == 'f') ? 0 : (int)(bool) $obj->$k) :  
  3237.                             $obj->$k
  3238.                     )));
  3239.                 continue;
  3240.             }
  3241.             if (is_numeric($obj->$k)) {
  3242.                 $this->whereAdd("{$joinAs}.{$kSql} = {$obj->$k}");
  3243.                 continue;
  3244.             }
  3245.                         
  3246.             if (is_a($obj->$k,'DB_DataObject_Cast')) {
  3247.                 $value = $obj->$k->toString($v,$DB);
  3248.                 if (PEAR::isError($value)) {
  3249.                     $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
  3250.                     return false;
  3251.                 }
  3252.                 if (strtolower($value) === 'null') {
  3253.                     $this->whereAdd("{$joinAs}.{$kSql} IS NULL");
  3254.                     continue;
  3255.                 } else {
  3256.                     $this->whereAdd("{$joinAs}.{$kSql} = $value");
  3257.                     continue;
  3258.                 }
  3259.             }
  3260.             
  3261.             
  3262.             /* this is probably an error condition! */
  3263.             $this->whereAdd("{$joinAs}.{$kSql} = 0");
  3264.         }
  3265.         if (!isset($this->_query)) {
  3266.             $this->raiseError(
  3267.                 "joinAdd can not be run from a object that has had a query run on it,
  3268.                 clone the object or create a new one and use setFrom()", 
  3269.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  3270.             return false;
  3271.         }
  3272.         // and finally merge the whereAdd from the child..
  3273.         if (!$obj->_query['condition']) {
  3274.             return true;
  3275.         }
  3276.         $cond = preg_replace('/^\sWHERE/i','',$obj->_query['condition']);
  3277.         
  3278.         $this->whereAdd("($cond)");
  3279.         return true;
  3280.  
  3281.     }
  3282.  
  3283.     /**
  3284.      * Copies items that are in the table definitions from an
  3285.      * array or object into the current object
  3286.      * will not override key values.
  3287.      *
  3288.      *
  3289.      * @param    array | object  $from
  3290.      * @param    string  $format eg. map xxxx_name to $object->name using 'xxxx_%s' (defaults to %s - eg. name -> $object->name
  3291.      * @param    boolean  $skipEmpty (dont assign empty values if a column is empty (eg. '' / 0 etc...)
  3292.      * @access   public
  3293.      * @return   true on success or array of key=>setValue error message
  3294.      */
  3295.     function setFrom(&$from, $format = '%s', $skipEmpty=false)
  3296.     {
  3297.         global $_DB_DATAOBJECT;
  3298.         $keys  = $this->keys();
  3299.         $items = $this->table();
  3300.         if (!$items) {
  3301.             $this->raiseError(
  3302.                 "setFrom:Could not find table definition for {$this->__table}", 
  3303.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  3304.             return;
  3305.         }
  3306.         $overload_return = array();
  3307.         foreach (array_keys($items) as $k) {
  3308.             if (in_array($k,$keys)) {
  3309.                 continue; // dont overwrite keys
  3310.             }
  3311.             if (!$k) {
  3312.                 continue; // ignore empty keys!!! what
  3313.             }
  3314.             if (is_object($from) && isset($from->{sprintf($format,$k)})) {
  3315.                 $kk = (strtolower($k) == 'from') ? '_from' : $k;
  3316.                 if (method_exists($this,'set'.$kk)) {
  3317.                     $ret = $this->{'set'.$kk}($from->{sprintf($format,$k)});
  3318.                     if (is_string($ret)) {
  3319.                         $overload_return[$k] = $ret;
  3320.                     }
  3321.                     continue;
  3322.                 }
  3323.                 $this->$k = $from->{sprintf($format,$k)};
  3324.                 continue;
  3325.             }
  3326.             
  3327.             if (is_object($from)) {
  3328.                 continue;
  3329.             }
  3330.             
  3331.             if (empty($from[$k]) && $skipEmpty) {
  3332.                 continue;
  3333.             }
  3334.             
  3335.             if (!isset($from[sprintf($format,$k)])) {
  3336.                 continue;
  3337.             }
  3338.            
  3339.             $kk = (strtolower($k) == 'from') ? '_from' : $k;
  3340.             if (method_exists($this,'set'. $kk)) {
  3341.                 $ret =  $this->{'set'.$kk}($from[sprintf($format,$k)]);
  3342.                 if (is_string($ret)) {
  3343.                     $overload_return[$k] = $ret;
  3344.                 }
  3345.                 continue;
  3346.             }
  3347.             if (is_object($from[sprintf($format,$k)])) {
  3348.                 continue;
  3349.             }
  3350.             if (is_array($from[sprintf($format,$k)])) {
  3351.                 continue;
  3352.             }
  3353.             $ret = $this->fromValue($k,$from[sprintf($format,$k)]);
  3354.             if ($ret !== true)  {
  3355.                 $overload_return[$k] = 'Not A Valid Value';
  3356.             }
  3357.             //$this->$k = $from[sprintf($format,$k)];
  3358.         }
  3359.         if ($overload_return) {
  3360.             return $overload_return;
  3361.         }
  3362.         return true;
  3363.     }
  3364.  
  3365.     /**
  3366.      * Returns an associative array from the current data
  3367.      * (kind of oblivates the idea behind DataObjects, but
  3368.      * is usefull if you use it with things like QuickForms.
  3369.      *
  3370.      * you can use the format to return things like user[key]
  3371.      * by sending it $object->toArray('user[%s]')
  3372.      *
  3373.      * will also return links converted to arrays.
  3374.      *
  3375.      * @param   string  sprintf format for array
  3376.      * @param   bool    empty only return elemnts that have a value set.
  3377.      *
  3378.      * @access   public
  3379.      * @return   array of key => value for row
  3380.      */
  3381.  
  3382.     function toArray($format = '%s', $hideEmpty = false) 
  3383.     {
  3384.         global $_DB_DATAOBJECT;
  3385.         $ret = array();
  3386.         $rf = ($this->_resultFields !== false) ? $this->_resultFields : 
  3387.                 (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]) ? $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid] : false);
  3388.         $ar = ($rf !== false) ?
  3389.             array_merge($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid],$this->table()) :
  3390.             $this->table();
  3391.  
  3392.         foreach($ar as $k=>$v) {
  3393.              
  3394.             if (!isset($this->$k)) {
  3395.                 if (!$hideEmpty) {
  3396.                     $ret[sprintf($format,$k)] = '';
  3397.                 }
  3398.                 continue;
  3399.             }
  3400.             // call the overloaded getXXXX() method. - except getLink and getLinks
  3401.             if (method_exists($this,'get'.$k) && !in_array(strtolower($k),array('links','link'))) {
  3402.                 $ret[sprintf($format,$k)] = $this->{'get'.$k}();
  3403.                 continue;
  3404.             }
  3405.             // should this call toValue() ???
  3406.             $ret[sprintf($format,$k)] = $this->$k;
  3407.         }
  3408.         if (!$this->_link_loaded) {
  3409.             return $ret;
  3410.         }
  3411.         foreach($this->_link_loaded as $k) {
  3412.             $ret[sprintf($format,$k)] = $this->$k->toArray();
  3413.         
  3414.         }
  3415.         
  3416.         return $ret;
  3417.     }
  3418.  
  3419.     /**
  3420.      * validate the values of the object (usually prior to inserting/updating..)
  3421.      *
  3422.      * Note: This was always intended as a simple validation routine.
  3423.      * It lacks understanding of field length, whether you are inserting or updating (and hence null key values)
  3424.      *
  3425.      * This should be moved to another class: DB_DataObject_Validate 
  3426.      *      FEEL FREE TO SEND ME YOUR VERSION FOR CONSIDERATION!!!
  3427.      *
  3428.      * Usage:
  3429.      * if (is_array($ret = $obj->validate())) { ... there are problems with the data ... }
  3430.      *
  3431.      * Logic:
  3432.      *   - defaults to only testing strings/numbers if numbers or strings are the correct type and null values are correct
  3433.      *   - validate Column methods : "validate{ROWNAME}()"  are called if they are defined.
  3434.      *            These methods should return 
  3435.      *                  true = everything ok
  3436.      *                  false|object = something is wrong!
  3437.      * 
  3438.      *   - This method loads and uses the PEAR Validate Class.
  3439.      *
  3440.      *
  3441.      * @access  public
  3442.      * @return  array of validation results (where key=>value, value=false|object if it failed) or true (if they all succeeded)
  3443.      */
  3444.     function validate()
  3445.     {
  3446.         require_once 'Validate.php';
  3447.         $table = $this->table();
  3448.         $ret   = array();
  3449.         $seq   = $this->sequenceKey();
  3450.         
  3451.         foreach($table as $key => $val) {
  3452.             
  3453.             
  3454.             // call user defined validation always...
  3455.             $method = "Validate" . ucfirst($key);
  3456.             if (method_exists($this, $method)) {
  3457.                 $ret[$key] = $this->$method();
  3458.                 continue;
  3459.             }
  3460.             
  3461.             // if not null - and it's not set.......
  3462.             
  3463.             if (!isset($this->$key) && ($val & DB_DATAOBJECT_NOTNULL)) {
  3464.                 // dont check empty sequence key values..
  3465.                 if (($key == $seq[0]) && ($seq[1] == true)) {
  3466.                     continue;
  3467.                 }
  3468.                 $ret[$key] = false;
  3469.                 continue;
  3470.             }
  3471.             
  3472.             
  3473.             if (is_string($this->$key) && (strtolower($this->$key) == 'null')) {
  3474.                 if ($val & DB_DATAOBJECT_NOTNULL) {
  3475.                     $this->debug("'null' field used for '$key', but it is defined as NOT NULL", 'VALIDATION', 4);
  3476.                     $ret[$key] = false;
  3477.                     continue;
  3478.                 }
  3479.                 continue;
  3480.             }
  3481.  
  3482.             // ignore things that are not set. ?
  3483.            
  3484.             if (!isset($this->$key)) {
  3485.                 continue;
  3486.             }
  3487.             
  3488.             // if the string is empty.. assume it is ok..
  3489.             if (!is_object($this->$key) && !is_array($this->$key) && !strlen((string) $this->$key)) {
  3490.                 continue;
  3491.             }
  3492.             
  3493.             // dont try and validate cast objects - assume they are problably ok..
  3494.             if (is_object($this->$key) && is_a($this->$key,'DB_DataObject_Cast')) {
  3495.                 continue;
  3496.             }
  3497.             // at this point if you have set something to an object, and it's not expected
  3498.             // the Validate will probably break!!... - rightly so! (your design is broken, 
  3499.             // so issuing a runtime error like PEAR_Error is probably not appropriate..
  3500.             
  3501.             switch (true) {
  3502.                 // todo: date time.....
  3503.                 case  ($val & DB_DATAOBJECT_STR):
  3504.                     $ret[$key] = Validate::string($this->$key, VALIDATE_PUNCTUATION . VALIDATE_NAME);
  3505.                     continue;
  3506.                 case  ($val & DB_DATAOBJECT_INT):
  3507.                     $ret[$key] = Validate::number($this->$key, array('decimal'=>'.'));
  3508.                     continue;
  3509.             }
  3510.         }
  3511.         // if any of the results are false or an object (eg. PEAR_Error).. then return the array..
  3512.         foreach ($ret as $key => $val) {
  3513.             if ($val !== true) {
  3514.                 return $ret;
  3515.             }
  3516.         }
  3517.         return true; // everything is OK.
  3518.     }
  3519.  
  3520.     /**
  3521.      * Gets the DB object related to an object - so you can use funky peardb stuf with it :)
  3522.      *
  3523.      * @access public
  3524.      * @return object The DB connection
  3525.      */
  3526.     function &getDatabaseConnection()
  3527.     {
  3528.         global $_DB_DATAOBJECT;
  3529.  
  3530.         if (($e = $this->_connect()) !== true) {
  3531.             return $e;
  3532.         }
  3533.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  3534.             $r = false;
  3535.             return $r;
  3536.         }
  3537.         return $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  3538.     }
  3539.  
  3540.  
  3541.     /**
  3542.      * Gets the DB result object related to the objects active query
  3543.      *  - so you can use funky pear stuff with it - like pager for example.. :)
  3544.      *
  3545.      * @access public
  3546.      * @return object The DB result object
  3547.      */
  3548.      
  3549.     function &getDatabaseResult()
  3550.     {
  3551.         global $_DB_DATAOBJECT;
  3552.         $this->_connect();
  3553.         if (!isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {
  3554.             $r = false;
  3555.             return $r;
  3556.         }
  3557.         return $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
  3558.     }
  3559.  
  3560.     /**
  3561.      * Overload Extension support
  3562.      *  - enables setCOLNAME/getCOLNAME
  3563.      *  if you define a set/get method for the item it will be called.
  3564.      * otherwise it will just return/set the value.
  3565.      * NOTE this currently means that a few Names are NO-NO's 
  3566.      * eg. links,link,linksarray, from, Databaseconnection,databaseresult
  3567.      *
  3568.      * note 
  3569.      *  - set is automatically called by setFrom.
  3570.      *   - get is automatically called by toArray()
  3571.      *  
  3572.      * setters return true on success. = strings on failure
  3573.      * getters return the value!
  3574.      *
  3575.      * this fires off trigger_error - if any problems.. pear_error, 
  3576.      * has problems with 4.3.2RC2 here
  3577.      *
  3578.      * @access public
  3579.      * @return true?
  3580.      * @see overload
  3581.      */
  3582.  
  3583.     
  3584.     function _call($method,$params,&$return) {
  3585.         
  3586.         //$this->debug("ATTEMPTING OVERLOAD? $method");
  3587.         // ignore constructors : - mm
  3588.         if (strtolower($method) == strtolower(get_class($this))) {
  3589.             return true;
  3590.         }
  3591.         $type = strtolower(substr($method,0,3));
  3592.         $class = get_class($this);
  3593.         if (($type != 'set') && ($type != 'get')) {
  3594.             return false;
  3595.         }
  3596.          
  3597.         
  3598.         
  3599.         // deal with naming conflick of setFrom = this is messy ATM!
  3600.         
  3601.         if (strtolower($method) == 'set_from') {
  3602.             $return = $this->toValue('from',isset($params[0]) ? $params[0] : null);
  3603.             return  true;
  3604.         }
  3605.         
  3606.         $element = substr($method,3);
  3607.         
  3608.         // dont you just love php's case insensitivity!!!!
  3609.         
  3610.         $array =  array_keys(get_class_vars($class));
  3611.         /* php5 version which segfaults on 5.0.3 */
  3612.         if (class_exists('ReflectionClass')) {
  3613.             $reflection = new ReflectionClass($class);
  3614.             $array = array_keys($reflection->getdefaultProperties());
  3615.         }
  3616.         
  3617.         if (!in_array($element,$array)) {
  3618.             // munge case
  3619.             foreach($array as $k) {
  3620.                 $case[strtolower($k)] = $k;
  3621.             }
  3622.             if ((substr(phpversion(),0,1) == 5) && isset($case[strtolower($element)])) {
  3623.                 trigger_error("PHP5 set/get calls should match the case of the variable",E_USER_WARNING);
  3624.                 $element = strtolower($element);
  3625.             }
  3626.             
  3627.             // does it really exist?
  3628.             if (!isset($case[$element])) {
  3629.                 return false;            
  3630.             }
  3631.             // use the mundged case
  3632.             $element = $case[$element]; // real case !
  3633.         }
  3634.         
  3635.         
  3636.         if ($type == 'get') {
  3637.             $return = $this->toValue($element,isset($params[0]) ? $params[0] : null);
  3638.             return true;
  3639.         }
  3640.         
  3641.         
  3642.         $return = $this->fromValue($element, $params[0]);
  3643.          
  3644.         return true;
  3645.             
  3646.           
  3647.     }
  3648.         
  3649.     
  3650.     /**
  3651.     * standard set* implementation.
  3652.     *
  3653.     * takes data and uses it to set dates/strings etc.
  3654.     * normally called from __call..  
  3655.     *
  3656.     * Current supports
  3657.     *   date      = using (standard time format, or unixtimestamp).... so you could create a method :
  3658.     *               function setLastread($string) { $this->fromValue('lastread',strtotime($string)); }
  3659.     *
  3660.     *   time      = using strtotime 
  3661.     *   datetime  = using  same as date - accepts iso standard or unixtimestamp.
  3662.     *   string    = typecast only..
  3663.     * 
  3664.     * TODO: add formater:: eg. d/m/Y for date! ???
  3665.     *
  3666.     * @param   string       column of database
  3667.     * @param   mixed        value to assign
  3668.     *
  3669.     * @return   true| false     (False on error)
  3670.     * @access   public 
  3671.     * @see      DB_DataObject::_call
  3672.     */
  3673.   
  3674.     
  3675.     function fromValue($col,$value) 
  3676.     {
  3677.         $cols = $this->table();
  3678.         // dont know anything about this col..
  3679.         if (!isset($cols[$col])) {
  3680.             $this->$col = $value;
  3681.             return true;
  3682.         }
  3683.         //echo "FROM VALUE $col, {$cols[$col]}, $value\n";
  3684.         switch (true) {
  3685.             // set to null and column is can be null...
  3686.             case ((strtolower($value) == 'null') && (!($cols[$col] & DB_DATAOBJECT_NOTNULL))):
  3687.             case (is_object($value) && is_a($value,'DB_DataObject_Cast')): 
  3688.                 $this->$col = $value;
  3689.                 return true;
  3690.                 
  3691.             // fail on setting null on a not null field..
  3692.             case ((strtolower($value) == 'null') && ($cols[$col] & DB_DATAOBJECT_NOTNULL)):
  3693.                 return false;
  3694.         
  3695.             case (($cols[$col] & DB_DATAOBJECT_DATE) &&  ($cols[$col] & DB_DATAOBJECT_TIME)):
  3696.                 // empty values get set to '' (which is inserted/updated as NULl
  3697.                 if (!$value) {
  3698.                     $this->$col = '';
  3699.                 }
  3700.             
  3701.                 if (is_numeric($value)) {
  3702.                     $this->$col = date('Y-m-d H:i:s', $value);
  3703.                     return true;
  3704.                 }
  3705.               
  3706.                 // eak... - no way to validate date time otherwise...
  3707.                 $this->$col = (string) $value;
  3708.                 return true;
  3709.             
  3710.             case ($cols[$col] & DB_DATAOBJECT_DATE):
  3711.                 // empty values get set to '' (which is inserted/updated as NULl
  3712.                  
  3713.                 if (!$value) {
  3714.                     $this->$col = '';
  3715.                     return true; 
  3716.                 }
  3717.             
  3718.                 if (is_numeric($value)) {
  3719.                     $this->$col = date('Y-m-d',$value);
  3720.                     return true;
  3721.                 }
  3722.                  
  3723.                 // try date!!!!
  3724.                 require_once 'Date.php';
  3725.                 $x = new Date($value);
  3726.                 $this->$col = $x->format("%Y-%m-%d");
  3727.                 return true;
  3728.             
  3729.             case ($cols[$col] & DB_DATAOBJECT_TIME):
  3730.                 // empty values get set to '' (which is inserted/updated as NULl
  3731.                 if (!$value) {
  3732.                     $this->$col = '';
  3733.                 }
  3734.             
  3735.                 $guess = strtotime($value);
  3736.                 if ($guess != -1) {
  3737.                      $this->$col = date('H:i:s', $guess);
  3738.                     return $return = true;
  3739.                 }
  3740.                 // otherwise an error in type...
  3741.                 return false;
  3742.             
  3743.             case ($cols[$col] & DB_DATAOBJECT_STR):
  3744.                 
  3745.                 $this->$col = (string) $value;
  3746.                 return true;
  3747.                 
  3748.             // todo : floats numerics and ints...
  3749.             default:
  3750.                 $this->$col = $value;
  3751.                 return true;
  3752.         }
  3753.     
  3754.     
  3755.     
  3756.     }
  3757.      /**
  3758.     * standard get* implementation.
  3759.     *
  3760.     *  with formaters..
  3761.     * supported formaters:  
  3762.     *   date/time : %d/%m/%Y (eg. php strftime) or pear::Date 
  3763.     *   numbers   : %02d (eg. sprintf)
  3764.     *  NOTE you will get unexpected results with times like 0000-00-00 !!!
  3765.     *
  3766.     *
  3767.     * 
  3768.     * @param   string       column of database
  3769.     * @param   format       foramt
  3770.     *
  3771.     * @return   true     Description
  3772.     * @access   public 
  3773.     * @see      DB_DataObject::_call(),strftime(),Date::format()
  3774.     */
  3775.     function toValue($col,$format = null) 
  3776.     {
  3777.         if (is_null($format)) {
  3778.             return $this->$col;
  3779.         }
  3780.         $cols = $this->table();
  3781.         switch (true) {
  3782.             case (($cols[$col] & DB_DATAOBJECT_DATE) &&  ($cols[$col] & DB_DATAOBJECT_TIME)):
  3783.                 if (!$this->$col) {
  3784.                     return '';
  3785.                 }
  3786.                 $guess = strtotime($this->$col);
  3787.                 if ($guess != -1) {
  3788.                     return strftime($format, $guess);
  3789.                 }
  3790.                 // eak... - no way to validate date time otherwise...
  3791.                 return $this->$col;
  3792.             case ($cols[$col] & DB_DATAOBJECT_DATE):
  3793.                 if (!$this->$col) {
  3794.                     return '';
  3795.                 } 
  3796.                 $guess = strtotime($this->$col);
  3797.                 if ($guess != -1) {
  3798.                     return strftime($format,$guess);
  3799.                 }
  3800.                 // try date!!!!
  3801.                 require_once 'Date.php';
  3802.                 $x = new Date($this->$col);
  3803.                 return $x->format($format);
  3804.                 
  3805.             case ($cols[$col] & DB_DATAOBJECT_TIME):
  3806.                 if (!$this->$col) {
  3807.                     return '';
  3808.                 }
  3809.                 $guess = strtotime($this->$col);
  3810.                 if ($guess > -1) {
  3811.                     return strftime($format, $guess);
  3812.                 }
  3813.                 // otherwise an error in type...
  3814.                 return $this->$col;
  3815.                 
  3816.             case ($cols[$col] &  DB_DATAOBJECT_MYSQLTIMESTAMP):
  3817.                 if (!$this->$col) {
  3818.                     return '';
  3819.                 }
  3820.                 require_once 'Date.php';
  3821.                 
  3822.                 $x = new Date($this->$col);
  3823.                 
  3824.                 return $x->format($format);
  3825.             
  3826.              
  3827.             case ($cols[$col] &  DB_DATAOBJECT_BOOL):
  3828.                 
  3829.                 if ($cols[$col] &  DB_DATAOBJECT_STR) {
  3830.                     // it's a 't'/'f' !
  3831.                     return ($this->$col == 't');
  3832.                 }
  3833.                 return (bool) $this->$col;
  3834.             
  3835.                
  3836.             default:
  3837.                 return sprintf($format,$this->col);
  3838.         }
  3839.             
  3840.  
  3841.     }
  3842.     
  3843.     
  3844.     /* ----------------------- Debugger ------------------ */
  3845.  
  3846.     /**
  3847.      * Debugger. - use this in your extended classes to output debugging information.
  3848.      *
  3849.      * Uses DB_DataObject::DebugLevel(x) to turn it on
  3850.      *
  3851.      * @param    string $message - message to output
  3852.      * @param    string $logtype - bold at start
  3853.      * @param    string $level   - output level
  3854.      * @access   public
  3855.      * @return   none
  3856.      */
  3857.     function debug($message, $logtype = 0, $level = 1)
  3858.     {
  3859.         global $_DB_DATAOBJECT;
  3860.  
  3861.         if (empty($_DB_DATAOBJECT['CONFIG']['debug'])  || 
  3862.             (is_numeric($_DB_DATAOBJECT['CONFIG']['debug']) &&  $_DB_DATAOBJECT['CONFIG']['debug'] < $level)) {
  3863.             return;
  3864.         }
  3865.         // this is a bit flaky due to php's wonderfull class passing around crap..
  3866.         // but it's about as good as it gets..
  3867.         $class = (isset($this) && is_a($this,'DB_DataObject')) ? get_class($this) : 'DB_DataObject';
  3868.         
  3869.         if (!is_string($message)) {
  3870.             $message = print_r($message,true);
  3871.         }
  3872.         if (!is_numeric( $_DB_DATAOBJECT['CONFIG']['debug']) && is_callable( $_DB_DATAOBJECT['CONFIG']['debug'])) {
  3873.             return call_user_func($_DB_DATAOBJECT['CONFIG']['debug'], $class, $message, $logtype, $level);
  3874.         }
  3875.         
  3876.         if (!ini_get('html_errors')) {
  3877.             echo "$class   : $logtype       : $message\n";
  3878.             flush();
  3879.             return;
  3880.         }
  3881.         if (!is_string($message)) {
  3882.             $message = print_r($message,true);
  3883.         }
  3884.         $colorize = ($logtype == 'ERROR') ? '<font color="red">' : '<font>';
  3885.         echo "<code>{$colorize}<B>$class: $logtype:</B> ". nl2br(htmlspecialchars($message)) . "</font></code><BR>\n";
  3886.         flush();
  3887.     }
  3888.  
  3889.     /**
  3890.      * sets and returns debug level
  3891.      * eg. DB_DataObject::debugLevel(4);
  3892.      *
  3893.      * @param   int     $v  level
  3894.      * @access  public
  3895.      * @return  none
  3896.      */
  3897.     function debugLevel($v = null)
  3898.     {
  3899.         global $_DB_DATAOBJECT;
  3900.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  3901.             DB_DataObject::_loadConfig();
  3902.         }
  3903.         if ($v !== null) {
  3904.             $r = isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0;
  3905.             $_DB_DATAOBJECT['CONFIG']['debug']  = $v;
  3906.             return $r;
  3907.         }
  3908.         return isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0;
  3909.     }
  3910.  
  3911.     /**
  3912.      * Last Error that has occured
  3913.      * - use $this->_lastError or
  3914.      * $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
  3915.      *
  3916.      * @access  public
  3917.      * @var     object PEAR_Error (or false)
  3918.      */
  3919.     var $_lastError = false;
  3920.  
  3921.     /**
  3922.      * Default error handling is to create a pear error, but never return it.
  3923.      * if you need to handle errors you should look at setting the PEAR_Error callback
  3924.      * this is due to the fact it would wreck havoc on the internal methods!
  3925.      *
  3926.      * @param  int $message    message
  3927.      * @param  int $type       type
  3928.      * @param  int $behaviour  behaviour (die or continue!);
  3929.      * @access public
  3930.      * @return error object
  3931.      */
  3932.     function raiseError($message, $type = null, $behaviour = null)
  3933.     {
  3934.         global $_DB_DATAOBJECT;
  3935.         
  3936.         if ($behaviour == PEAR_ERROR_DIE && !empty($_DB_DATAOBJECT['CONFIG']['dont_die'])) {
  3937.             $behaviour = null;
  3938.         }
  3939.         $error = &PEAR::getStaticProperty('DB_DataObject','lastError');
  3940.         
  3941.         // this will never work totally with PHP's object model.
  3942.         // as this is passed on static calls (like staticGet in our case)
  3943.  
  3944.         if (isset($this) && is_object($this) && is_subclass_of($this,'db_dataobject')) {
  3945.             $this->_lastError = $error;
  3946.         }
  3947.  
  3948.         $_DB_DATAOBJECT['LASTERROR'] = $error;
  3949.  
  3950.         // no checks for production here?....... - we log  errors before we throw them.
  3951.         DB_DataObject::debug($message,'ERROR',1);
  3952.         
  3953.         
  3954.         if (PEAR::isError($message)) {
  3955.             $error = $message;
  3956.         } else {
  3957.             require_once 'DB/DataObject/Error.php';
  3958.             $error = PEAR::raiseError($message, $type, $behaviour,
  3959.                             $opts=null, $userinfo=null, 'DB_DataObject_Error'
  3960.                         );
  3961.         }
  3962.    
  3963.         return $error;
  3964.     }
  3965.  
  3966.     /**
  3967.      * Define the global $_DB_DATAOBJECT['CONFIG'] as an alias to  PEAR::getStaticProperty('DB_DataObject','options');
  3968.      *
  3969.      * After Profiling DB_DataObject, I discoved that the debug calls where taking
  3970.      * considerable time (well 0.1 ms), so this should stop those calls happening. as
  3971.      * all calls to debug are wrapped with direct variable queries rather than actually calling the funciton
  3972.      * THIS STILL NEEDS FURTHER INVESTIGATION
  3973.      *
  3974.      * @access   public
  3975.      * @return   object an error object
  3976.      */
  3977.     function _loadConfig()
  3978.     {
  3979.         global $_DB_DATAOBJECT;
  3980.  
  3981.         $_DB_DATAOBJECT['CONFIG'] = &PEAR::getStaticProperty('DB_DataObject','options');
  3982.  
  3983.  
  3984.     }
  3985.      /**
  3986.      * Free global arrays associated with this object.
  3987.      *
  3988.      *
  3989.      * @access   public
  3990.      * @return   none
  3991.      */
  3992.     function free() 
  3993.     {
  3994.         global $_DB_DATAOBJECT;
  3995.           
  3996.         if (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  3997.             unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]);
  3998.         }
  3999.         if (isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {     
  4000.             unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  4001.         }
  4002.         // clear the staticGet cache as well.
  4003.         $this->_clear_cache();
  4004.         // this is a huge bug in DB!
  4005.         if (isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  4006.             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->num_rows = array();
  4007.         }
  4008.         
  4009.     }
  4010.     
  4011.     
  4012.     /* ---- LEGACY BC METHODS - NOT DOCUMENTED - See Documentation on New Methods. ---*/
  4013.     
  4014.     function _get_table() { return $this->table(); }
  4015.     function _get_keys()  { return $this->keys();  }
  4016.     
  4017.     
  4018.     
  4019.     
  4020. }
  4021. // technially 4.3.2RC1 was broken!!
  4022. // looks like 4.3.3 may have problems too....
  4023. if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) {
  4024.  
  4025.     if ((phpversion() != '4.3.2-RC1') && (version_compare( phpversion(), "4.3.1") > 0)) {
  4026.         if (version_compare( phpversion(), "5") < 0) {
  4027.            overload('DB_DataObject');
  4028.         } 
  4029.         $GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = true;
  4030.     }
  4031. }
  4032.  
  4033.